grails – Visionmate – En fullservice digital partner

Visma.net med Grails

E-handeln växer snabbt och många företag letar efter lösningar som kan automatisera alla processer så som komplexa finansiella transaktioner och avstå från repetitiv redovisning. Det finns en hel del tillgängliga verktyg på marknaden men bland de mer populära hittar vi i Visma.net Financials. Det är en ekonomilösning som har en flexibel konfiguration och fungerar väl för globala organisationer med krav på multinationell rapportering. I det här inlägget tänkte jag beröra dataflödet i det systemet.

Som backend använder jag den senaste versionen av Grails-framework (3.3.5) och för att göra HTTP-requests kommer jag att arbeta med modern Groovy DSL ”HttpBuilder-NG”. Du kan hitta mer information om Grails-framework på https://grails.org/.

Först behöver vi inkludera rätt library i vårt projekt för att skapa HTTP- requests. Grails använder Gradle som default builder, så lägg bara till en rad för dependencies i filen build.gradle.

compile "io.github.http-builder-ng:http-builder-ng-core:1.0.3"

Jag vill också nämna credentials. I det här fallet krävs företags-ID och access token.

Sedan behöver vi en tjänst som samlar alla actions för att ta emot och skicka data. Denna tjänst bör vara multifunktionell eftersom vi vill återanvända det med olika API-förfrågningar. För att kontrollera alla tillgängliga API:er, besök https://integration.visma.net/API-index/#.

Det finns en del att välja på men idag kommer vi att arbeta med dessa två: inventering (inventory) och bilaga (attachment). I denna artikel strävar vi bara efter att få data, låt oss som exempel kolla hur man hämtar listan över produkter som ditt företag säljer.

def getFromVisma(String target, def params = [:]) {

   String requestUri = "https://integration.visma.net"

   String requestPath= "/API/controller/api/v1"

   def vismaHeaders = [

           'Accept': "application/json",

           'ipp-company-id': "your company id"

           'ipp-application-type': "Visma.net Financials",

           'Authorization': "your own token"

   ]

   def http = configure {

       request.uri = requestUri

       request.uri.path = requestPath + target

       request.uri.query = params

       request.headers = vismaHeaders

   }

   return http.get {

       response.exception { ex ->

           println ex

       }

   }

}

Controller action för anrop kan se ut såhär:

class AnyController {

    VismaConnectService vismaConnectService

    def getFromVisma() {

        String target = '/inventory'

        def vismaData = vismaConnectService.getFromVisma(target)

        vismaData.each { data ->

            if (data.status == "Active") {
                
                // do something //

            }

        }

    }

}

Vår service method innehåller två parametrar: den första är ”target”. När du exempelvis requestar lagerobjekt, ska target vara ”inventering”. Den andra är ”map” som default är tom. Vanligtvis kan du i parametrarna ange alla de servicefält som Visma erbjuder som exempelvis ”lastModifiedDateTime”. Eftersom vi i header angav ”application/json” så är resultatet en lista med JSON-objekt och det ser ut så här:

{

        "inventoryId": 00001,

        "inventoryNumber": "00001",

        "status": "Active/Inactive",

        "type": "",

        "description": "",

        "defaultPrice": 0,

        "pendingCost": 0,

        "currentCost": 0,

        "lastCost": 0,

        "lastModifiedDateTime": "2018-01-01T00:00:00.00",

        "attachments": [

            {

                "name": "attachmentName",

                "id": "attachmentId",

                "revision": 1

            }

        ],

        "attributes": [ ],

}

Ovanstående är ett exempel och du kan ha fler eller färre fält. I det här exemplet markerade jag attachmentName och attachmentId. Vi behöver dessa parametrar för att få bifogade filer. Nu tittar vi på koden nedan:

def getAttachment(String attachmentName, String attachmentId) {

   String requestUri = "https://integration.visma.net"

   String requestPath= "/API/controller/api/v1/attachment"

   def vismaHeaders = [

           'ipp-company-id': "your company id",

           'ipp-application-type': "Visma.net Financials",

           'Authorization': "Bearer your own token"

   ]

   def http = configure {

       request.uri = requestUri

       request.uri.path = requestPath + attachmentId

       request.headers = vismaHeaders

   }

   return http.get {

       Download.toFile(delegate, new File("./attachments/${attachmentName}"))

       response.exception { ex ->

           println ex

       }

   }

}

Den sista koden är väldigt lik den första. Största skillnaden är dock att vi kommer att få en File och inte JSON. Men också istället för en File så kan vi få en TempFile eller en Stream. Det beror lite på ditt egna case och requirements.

Slutsats

Eftersom Visma erbjuder mycket större funktionalitet så är exemplen ovan bara toppen på ett isberget. Men du kan absolut använda ovanstående skeleton för att skapa komplexa lösningar för dina olika affärsuppgifter. Likväl, som visat ovan, kan Groovys kraft kombinerat med Grails flexibilitet med sina många plugins hjälpa dig att arbeta med data på ett mycket snabbt och effektivt sätt.

/ Olek, Javautvecklare på Visionmate

Alla artiklar

Push-notiser i Grails & React.js baserade webbprojekt

Det här inlägget riktar sig kanske mer mot dig som utvecklare än dig som beställare. Som oftast med system så krävs det en kommunikation mellan servern och klienten. Det är viktigt eftersom olika händelser kan uppstå på servern och en anmälan ska då skickas till klienten. Ett enkelt user cace är en chatt, där användarna kan utbyta meddelanden mellan varandra. Vi kan också använda annan teknik, till exempel Message Queue system. Beroende på behoven så kan man välja den man har mest användning för.

Beskrivning

I det här inlägget kommer vi att diskutera push-meddelanden med hjälp av WebSocket. Jag vill beskriva pedagogiken bakom arbetet, när det kommer till leverans och produktion så har vi självklart enklare rutiner på företaget för att bemöta varje beställning.

Mål

Att kunna genomföra push-notiser i webbapplikationer

Krav

Grails 3.* (Dependencies: Grails Spring WebSocket, Grails Spring Security Rest)

React.js (Dependencies: Sock.js client, Stomp.js)

 

1 – Server konfiguration

Vi måste konfigurera WebSocket message agent för att låta vårt system skicka och ta emot meddelanden via säkra kanaler.

Följande kod (configureClientInboundChannel metod) visar hur man autentiserar via WebSocket. Vi måste vara säkra på att endast en auktoriserad användare kan ansluta, prenumerera och ta emot meddelanden från vår server. Vårt system använder Spring Security Rest och följaktligen är användarna autentiserade via access token i varje förfrågan.

Vi konfigurerar också den endpoint där vår message agent lyssnar på subscription requests och meddelanden (registerStompEndpoints method)

import grails.plugin.springsecurity.rest.token.storage.TokenStorageService
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.messaging.simp.SimpMessageType
import org.springframework.messaging.simp.config.ChannelRegistration
import org.springframework.messaging.simp.stomp.StompHeaderAccessor
import org.springframework.messaging.support.ChannelInterceptorAdapter
import org.springframework.messaging.support.MessageBuilder
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker
import org.springframework.web.socket.config.annotation.StompEndpointRegistry
import org.springframework.messaging.Message
import org.springframework.messaging.MessageChannel
 

@Configuration
@EnableWebSocketMessageBroker
class DefaultWebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

   public static final String AUTHORIZATION = 'Authorization'
   public static final String BEARER = 'Bearer'

   @Autowired
   TokenStorageService tokenStorageService
 

@Override
void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {
   stompEndpointRegistry.addEndpoint("/ws")
         .setAllowedOrigins("*")
         .withSockJS()
         .setSessionCookieNeeded(false)
}
 

void configureClientInboundChannel(ChannelRegistration registration) {
   registration.setInterceptors(new ChannelInterceptorAdapter() {
      Message<Object> preSend(Message<Object> message,  MessageChannel channel) {
         String token = null
         Boolean access = false
         StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message)
         List tokens = accessor.getNativeHeader(AUTHORIZATION)
         accessor.removeNativeHeader(AUTHORIZATION)

         if(tokens?.size > 0) {
            token = tokens[0]
         }

         if (token?.startsWith(BEARER)) {
            token = token.substring(BEARER.length()).trim()
         }

         switch (accessor.messageType){
            case SimpMessageType.CONNECT: access = (tokenStorageService.loadUserByToken(token))?true:false
               break
            case SimpMessageType.SUBSCRIBE: access = (tokenStorageService.loadUserByToken(token))?true:false
               break
         }
         accessor.setLeaveMutable(true)
         return (access)?MessageBuilder.createMessage(message.payload, accessor.messageHeaders):null
      }
   })
}

2 – Skicka meddelanden

Vi använder Grails Spring WebSocket vilket ger oss den funktionaliteten som behövs för att kunna använda konfigurationen.

I vårt exempel kommer meddelandena att ha en relaterad åtgärd, för att radera, uppdatera eller skapa meddelanden i klienten. Detta är också möjligt om vi skapar olika ämnen för det ändamålet, men i det här fallet är det inte relevant. BrokerService ansvarar för att skicka meddelanden till en destination med hjälp av brokerMesssagingService.

class BrokerService {

    def brokerMessagingTemplate
    /**
     * Send message to destination
     * @param destination
     * @param message
     */
    void send(String destination, Map message) {
        brokerMessagingTemplate.convertAndSend destination, (message as JSON) as String
    }
}

3 – Web client configuration

I webbklienten använder vi Sock.js och Stomp.js för att prenumerera på vår meddelandekanal. För att prenumerera på en kanal i connection request så skickar vi header configuration med användarens token som behörighet.

Vi ställer som en parameter en callback-funktion som kommer att utföras efter att ha mottagit ett meddelande från servern. Med unsubscribe method kan du ta bort en prenumeration, användaren kommer då inte längre att få push-meddelanden när det är gjort.

import {SERVER_URL} from "../config";
import headers from "../security/headers";
import SockJS from 'sockjs-client';
import Stomp from 'stompjs';

/**
 * WebSockets support
 */
const WebSocket = (endpoint) => {

    const socket = SockJS(`${SERVER_URL}/${endpoint}`);
    const client = Stomp.over(socket);

    function subscribe(destination, callback) {
        client.connect(headers(), function(frame) {
            client.subscribe(destination, function(message){
              callback(JSON.parse(message.body))
              }, headers());
           });
        return client;
    }

    function unsubscribe(client){
        for (const sub in client.subscriptions) {
            if (client.subscriptions.hasOwnProperty(sub)) {
                client.unsubscribe(sub);
            }
        }
    }

    return {
        subscribe,
        unsubscribe
    }

};

export default WebSocket;

4 – Prenumeration och mottagande av anmälningar

Vi lägger subscription call på componentDidMount med en klass baserad på React Component.

Vi måste också implementera funktionaliteten som ska utföras när vi får notiserna.

import React from 'react';
import WebSocket from "../../api/WebSocket";
import {ACTION_STATUS} from '../../constant';
import MessageList from "../../components/MessageList";
import { notifyAddedMessage, notifyDeletedMessage,  notifyUpdatedMessage} from "../../actions/actions";


/**
 * Real-time messages
 */
class MessagePanel extends React.Component {

    componentDidMount() {
        this.subscribe();

    }

    componentWillUnmount() {
        if(this.state.client !== undefined)
            WebSocket('ws').unsubscribe(this.state.client);
    }

    subscribe() {
        const callback = (message) => {
            switch (message.action){
                case ACTION_STATUS.DELETED:
                    this.props.notifyDeletedMessage(message.id);
                    break;
                case ACTION_STATUS.UPDATED:
                    this.props.notifyUpdatedMessage(message);
                    break;
                default:
                    this.props.notifyAddedMessage(message);
            }
        };
        callback.bind(this);

        let client;
        if (user instanceof Object) {
            client = WebSocket('ws').subscribe(‘/topic/messages’, callback);
            this.setState({client});
        }
    }

    render() {
        return (
            <MessageList/>
        )
    }
}

Slutsats

Push-meddelanden gör systemet attraktivt och mer användbart, det minskar också serverns latency och förbättrar användarupplevelsen. I många fall kan vi, på ett enkelt sätt och med ett bra resultat, lägga till det med hjälp av WebSockets .

Referens

https://docs.spring.io/spring/docs/5.0.0.BUILD-SNAPSHOT/spring-framework-reference/html/websocket.html

 

/Carlos Senior Developer på Visionmate

Alla artiklar

Aspektorienterad programmering med Spring AOP över Grails framework

Förord

Aspektorienterad programmering (AOP) är ett nytt koncept och programmeringsparadigm. Det tillåter i princip möjligheten att lägga till funktionalitet till moduler och klasser utan att ändra i koden. Det möjliggörs genom att konfigurera Pointcut, som anger vilka bitar av koden som ska bli executed efter att ha skickat en viss händelse över en metod eller klass.

Mål

Vi strävar efter och antar att utvecklaren, med hjälp av denna artikel, kommer att kunna använda aspektorienterad programmering i Grails-projekt.

Requirements

Grails 3. *

Dependencies:

Spring Aspects 5. *

AspectJ Weaver 1. *

1- Aspektorienterad programmering approach

Oftast innebär våra mjukvarulösningar komplex logik och i detta fall är det särskilt viktigt att hålla en ren och förståelig kod. AOP som tillvägagångssätt hjälper till och möjliggör ett enkelt sätt för modularisering. I grund och botten så tillåter AOP att lägga till funktionalitet till moduler och klasser utan att ändra koden.

2- Bean setting med Spring

Det första steget är att konfigurera Spring AOP, i Spring resource file (conf / spring / resources.groovy). Vi behöver också ange vilken bean som ska fungera som aspect, i vårt fall kommer det att vara Trace bean. Den kan konfigureras på flera sätt, till exempel med annotation @Aspect in din Bean declaration.

I det här fallet måste vi injecta log dependencies som redan finns för Grails, så vi behöver inte skapa några instances själva.

// Aspect-Oriented

xmlns aop:"http://www.springframework.org/schema/aop"

trace(Trace){

    log = log

}

aop{

    config("proxy-target-class": true) {

        aspect(id: "traceService", ref: "trace")

    }

}

3- Implementing logging functionality

Några av de viktigaste begreppen vi behöver ha koll på är: Pointcut, Advice och JoinPoint. En Pointcut är huvudsakligen ett sätt att matcha en method call med en funktionalitet som vi lägger till ett nytt beteende, det behöver ange expression och signature. För att utföra det nya beteendet så behöver vi ange några advice och associera den med en Pointcut.

Advice är declared med annotations som exempelvis @Before och @After och anger när koden ska verkställas. Advice kommer att ta som parameter en JoinPoint, som innehåller information om det aktuella anropet, då menar jag info så som parametervärden, called metod osv.

Testprogrammet är relaterat till dokumenthanteringen. De viktigaste funktionerna är uppladdning, konvertering till HTML och att lagra dokument från olika format. Koden som vi visar ger prov på att vi för track actions över dokumenten.

Här hittar du Aspect bean kallad Trace:

@Aspect

class Trace {

    def log


    @Pointcut("execution(* my.company.myApp.document.DocumentService.add(..))")

    void createDocument(){}



    @Pointcut("execution(* my.company.myApp.document.DocumentService.update(..)) && args(document, user,..)")

    void updateDocument(Document document, User user){}



    @Pointcut("execution(* my.company.myApp.repository.RepositoryService.add*(..))")

    void storeDocument(){}



    @Before("createDocument()")

    void create(JoinPoint jp){

        Document document = jp.args[0] as Document

        User user = jp.args[1] as User

        log.info "The document: ${document?.name} has been created by user: ${user?.username}"

    }



    @Before("updateDocument(document, user)")

    void update(Document document, User user){

        log.info "The document: ${document?.name} has been modified by user: ${user?.username}"

    }

    

    @Before("storeDocument()")

    void storeDocument(JoinPoint jp){

        log.info "Called ${jp.signature.name} with ${jp.args} on ${jp.target}: Storing a new document"

    }
}

Jag har tagit upp tre Pointcut sentences, Jag kommer att förklara en del nedan.

Uppdatering av dokument:

@Pointcut("execution(* my.company.myApp.document.DocumentService.update(..)) && args(document, user,..)")
  
  void updateDocument(Document document, User user){}

I det här fallet anger vi vilken metod vi vill spåra. Vi kan ange arguments i Pointcut declaration, det funkar de flesta gånger.

Vår applikation innehåller method med texten:

void update(Document document, User user){

    Version version = new Version(state: State.UPDATED, user: user, date: new Date())

    document.addToVersions(version)

    document.save()   

}

Sedan efter declaring vår Pointcut så måste vi genomföra den metod som ska utföra required behavior. Vi vill lägga till nytt beteende exakt innan metoden exekveras och sedan spåra vilken användare som har uppdaterat dokumentet.

@Before("updateDocument(document, user)")

    void update(Document document, User user){
      
        log.info "The document: ${document?.name} has been modified by user: ${user?.username}"
    
    }

Lagring av dokument:

I det här nya fallet kommer vi att använda samma endpoint för att fånga ett annat method call:

@Pointcut("execution(* my.company.myApp.repository.RepositoryService.add*(..))")
    
    void storeDocument(){}

I vårt system har vi en Repository-klass som ansvarar för att arbeta med vårt files repository, i princip har vi två slags repository, HTML och Source. Vi vill spåra när ett dokument lagras i det.

def addHtmlDocument(String id, Format format, String fromPath){

    String repositoryPath = grailsApplication.config.dataStore.repository.html

    repository = new HtmlRepository(

            path: repositoryPath,

            converter: new DocumentFormat(format: Format.HTML),    

            builder: new AntBuilder())

    repository.addDocument(id, format, fromPath)

}


def addSourceDocument(String id, Format format, String fromPath){

    String repositoryPath = grailsApplication.config.dataStore.repository.source   

    repository = new SourceRepository(

            path: repositoryPath,

            builder: new AntBuilder())

    repository.addDocument(id, format, fromPath)

}

Behavior:

@Before("storeDocument()")

    void storeDocument(JoinPoint jp){

        log.info "Called ${jp.signature.name} with ${jp.args} on ${jp.target}: Storing a new document"

    }

Det nya handlingssättet kommer att skriva ut en logg med namnet på called method, arguments och instance description.

4- Slutsats

Aspektorienterad programmering (AOS) är ett nytt koncept, genom att använda det korrekt så ser vi till att vår kod hålls ren. Med AOS kan vi lägga till funktionalitet enklare utan att ändra befintliga klasser och metoder. Det är nödvändigt vara försiktig och hålla en ordentlig dokumentation för att undvika oönskat beteende i vår i vår applikation.

5- Referencer

https://docs.spring.io

Origin: www.linkedin.com

/ Carlos

Senior Web Application Developer på Visionmate

Alla artiklar

Kontinuerlig integration och Kontinuerlig Deployment med Jenkins Pipeline i Grails projects

Inledning

Programutveckling är spännande, men det är också ett yrke där du ständigt behöver utveckla och bredda din kompetens och ofta tänka utanför boxen. Förut hade mjukvaruföretagen bekymmer med att utveckla och leverera programvara snabbt och med bibehållen kvalitet. Det ledde så klart till att en stor andel projekt misslyckades. Sakta men säkert började man dock komma på metoder och processer för att upprätthålla flödet av s.k. hållbar utveckling.

För att vara konkurrenskraftig nu och/eller inom en snar framtid är det viktigt att möta efterfrågan på högkvalitativ mjukvara och implementera teknik som gör det möjligt för verksamheten att göra jobbet. Det finns många verktyg som kan hjälpa företaget att utveckla sina processer och hålla snabba kvalitativa lanseringar. Jag kommer i det här inlägget berätta om det kontinuerliga integrationsverktyget Jenkins, vilket är ett open source-verktyg som har många nyttiga och användbara tillägg. Jag kommer att visa hur man skapar en pipeline (Jenkins Declarative Pipeline) som ger en flexibel och kraftfull metod för att bygga, testa och deploya programvaror.

Mål

Skaffa allmän kunskap om kontinuerliga integrationsverktyg i mjukvaruprocessen, kunna automatisera integreringen och deployment av software pieces.

Systemkrav

Jenkins 2.73.3 (Om du använder Linux OS behöver du byta Batch skriptet mot Shell skriptet)

Grails 3. *

1.0. Kontinuerlig integration och Deployment strategi

Kontinuerlig integration är en mjukvaruutvecklingspraxis där medlemmar i ett team ofta samordnar sitt arbete. Vanligtvis integrerar varje person åtminstone på daglig basis, vilket leder till flera integreringar per dag. Varje integrering verifieras automatiskt (inklusive test) för att upptäcka eventuella integrationsfel så snabbt som möjligt (Martin Fowler, 2006).

Utvecklarna bör fokusera på utveckling och inte på implementeringsprocessen. Utvecklarens roll är att leverera kvalitetskod och programvara, när koden väl finns på plats så är utvecklaren inte ansvarig för den mer. Det låter ju bra tänker du, men hur löser man det? Det är inte en bra lösning att göra det manuellt, anledningen är att du då måste skapa en lokal miljö för att bygga och deploya mjukvaran. Om programvaran visar sig vara mer komplex så blir också byggnads- och installationsprocessen mer komplicerad. Det bästa sättet är därför att automatisera hela processen, i det här fallet kan en DevOps-ingenjör i ditt team med all säkerhet lösa processen. När koden väl finns på plats kommer den automatiskt att kompileras och deployas i alla olika miljöer med hjälp av varierande tekniker och verktyg. Utvecklarna kan då koncentrera sig på just utvecklingen.

2.0. Jenkins

Jenkins är ett välkänt verktyg för kontinuerlig integration. Verktyget har öppen källkod och gratis, det har även ett ganska stort utvecklar-Community. Med Jenkins kan du bygga ditt projekt, testa och deploya det i dina olika miljöer.

I verktyget är det möjligt att skapa olika typer av jobb eller objekt, bland dem kan du hitta ”Freestyle-projektet” där du kan använda olika moduler för att bygga och deploya din programvara. ”Jenkins pipeline” är ett mycket enkelt sätt att dra fördel av alla egenskaper hos Jenkins. Jenkins pipeline är dessutom mer flexibelt och anpassningsbart. I det här inlägget vill jag visa ett exempel på det.

2.1. Jenkins pipeline

Jenkins pipeline är ett Groovy-skript (Groovy är ett dynamiskt och modernt språk som översätts av Java Virtual Machine (JVM)). Pipen kan vara deklarativ eller scriptad, skillnaden är att deklarativ måste följa en viss syntax och den scriptade bör inte göra det. I det här fallet kommer vi att använda Declarative Pipeline. Declarative Pipeline har principiellt en enkel och användbar struktur där du kan dela upp ditt arbete steg för steg.

Nedan ser du en deklarativ Pipeline-struktur:

pipeline {
    agent any 
    stages {
        stage('Stage name') {
            steps{ 
            }
        } 
    }
}

Agent syntax specificerar var och hur pipen körs. Du kan lägga in många faser och var och en innehåller olika steg. I stegsyntaxen beskriver du den åtgärd du vill göra. Den deklarativa pipen är bred och innehåller många användbara syntaxer (se: https://jenkins.io/doc/book/pipeline).

Pipen kan implementeras i ett Jenkins-jobb eller som en Jenkinsfil i källan till ditt projekt, den senare är bara en fil som innehåller din deklarativa pipeline (se: https://jenkins.io/doc/book/pipeline/jenkinsfile/ ). Vidare kommer vi att använda Pipeline inom ett Jenkins-jobb.

2.1.1. Uppdatera från version control repository

I detta skede uppdaterar vi källkoden. Vi använder en Git-modul för att hantera den, den finns installerad som standard. Vi behöver bara ange vilken filial vi ska använda, webbadressen till repository och åtkomst-id. Vi kan konfigurera vår access-id i avsnittet ”Credentials”.

stage(’Pull’) {

    steps{
        git(branch: 'myBranch', 
            credentialsId: 'myId', url: 'https://github.com/myCompany/MyProject')
    }
}

2.1.2. Bygg ditt projekt

I det här skedet bygger vi projektet genom att köra ett Grails-kommando med batch modul. Genom att använd Grails i projektet så behöver vi bara köra ett kommando för att bygga det och ange var vi ska skapa vår .war-fil.

Senare måste vi arkivera vår artefakt för att få den tillgänglig för deployment eller något annat, vi använder wildcards för att ange sökvägen till artefakten.

stage('Build') {
    steps{
        bat(script: 'grails war', returnStatus: true)
        archiveArtifacts '**/build/libs/*.war'
    }
}

2.1.3. Testa ditt projekt

Grails 3. * använder Gradle som verktyg och med andra instrument, som exempelvis Ant och Maven, kan vi implementera tasks genom att anropa dem direkt eller i olika steg. När vår .war artefakt skapas kan vi testa vår mjukvara automatiskt. I det här fallet kan du köra ditt test i byggprocessen. I det här inlägget testar vi efter att ha byggt vårt projekt med en ny fas i Jenkins Pipeline.

stage('Test') { 
    when {
        expression { 
            def status = bat(returnStdout: true, script: 'grails test-app my.package.MyTestClass.myTestMethod | findstr BUILD')
            
            if(status.contains('BUILD') && status.contains('FAILED'))
                return true
               else 
                return false
        }
    }
    steps{
        error("TEST FAILED")
    }

}

I detta steg använde vi ett villkorat steg för att göra hela processen misslyckad om testet misslyckades.

2.1.4. Deploy

I det här fallet har vi Jenkins körande på samma server som vår applikationsserver och det är tillräckligt för att kopiera vår artefakt.

stage('Deploy') {
    steps {
        copyArtifacts(filter: '**/build/libs/*.war', flatten: true, projectName: 'MyProject', target: 'C:/www/MyProject', selector: specific('${BUILD_NUMBER}'))
    }
}

Med ”selector: specific (’$ {BUILD_NUMBER}’)” kommer vi att vara säkra på att kunna deploya vår artefakt (som genererats av det aktuella bygget).

2.2. Hook triggers

I avsnittet ”Build Triggers” kan du se över ”GitHub hook trigger för GITScm polling” options och i dina Github projektinställningar måste du lägga till en ny webhook med en hemlig nyckel som du behöver konfigurera även i Jenkins. Efter det, när Github upptäcker någon förändring i din filial, skickar den en notification till Jenkins som startar upp ditt jobb.

2.3. Ytterligare konfigurationer och tips

Om du använder Tomcat och får problem med att automatiskt deploya .war-filer så kan du lägga till nya steg i din pipeline:

stage('PreDeploy') {
    when {
        expression { 
            def status = bat(returnStdout: true, script: 'sc query Tomcat8 | findstr RUNNING || echo STOPPED')
           
            if(status.contains('STATE') && status.contains('RUNNING'))
                return true
               else 
                return false
        }
    }
    steps {
        bat(script:'net stop Tomcat8')
    } 
}

stage('Deploy') {
    steps {
        copyArtifacts(filter: '**/build/libs/*.war', flatten: true, projectName: 'MyProject', target: 'C:/www/MyProject', selector: specific('${BUILD_NUMBER}'))
    }
}

stage('PostDeploy') {
    steps {
        bat(script:'net start Tomcat8')
    }
}

Sammanfattning

Den tid du lägger på att förbättra utvecklingsprocessen, till exempel för att genomföra kontinuerlig integration och kontinuerlig deployment, är aldrig bortkastad och det kommer att säkerställa snabbare utgåvor, också ett fokus på utvecklingsprocesser och naturligtvis en nöjd kund. Jenkins pipeline erbjuder oss ett kraftfullt och enkelt sätt att göra det.

 / Carlos

 

Kommentera eller fråga gärna på mail:

info(@)visionmate.se

 

Referenser

Martin Fowler, 2006: Continuous Integration, https://www.martinfowler.com/articles/continuousIntegration.html

Alla artiklar