Den nya nollnivån

Arbetet relaterat till Continuous Delivery har lett oss till en allt tydligare insikt om den grundfunktionalitet som bör finnas på plats i en kör- och utvecklingsmiljö redan från start, för att vi direkt ska kunna börja producera funktionalitet relaterad till kundnytta utan att börja ackumulera teknisk skuld.

Denna utvecklings- och körmiljö kan man också kalla Build Pipeline. Den omfattar såväl verktyg runtomkring systemet i fråga (till exempel Jenkins, Gradle eller Maven) som aspekter av själva systemet. En Build Pipeline av denna typ är att sträva efter såväl om man levererar systemet varje dag som en gång per kvartal.

Summan av den grundfunktionaliteten är vad jag här betecknar som nollnivån.

level-of-zero

 

Utvecklingsteam som arbetar i miljöer som saknar en eller flera av de egenskaper som beskrivs här kan naturligtvis också leverera nyttig funktionalitet, men dras med större eller mindre nackdelar, till exempel:

  • lägre utvecklingsfart
  • lägre kvalitet och sämre stabilitet
  • längre ledtid för ny funktionalitet
  • arbetsamma och riskfyllda produktionssättningar

Enligt mina iakttagelser krävs än så länge mycket erfarenhet och bred teknisk kunskap för att täcka upp de lösningar och mekanismer som krävs för att nå denna nollnivå för ett system och dess build pipeline.

I dagsläget är det inte säkert att det för alla system lönar sig att först ta sig till nollnivån innan man börjar ta sig an att implementera användningsfallen. Det beror på många olika faktorer, kanske främst systemets komplexitetsgrad och i hur hög grad systemet som utvecklas utgör en del av organisationens kärnverksamhet.

Vartefter lösningarna för att realisera en Build Pipeline med de egenskaper som beskrivs här blir enklare och mer standardiserade kommer det att löna sig att lyfta in mer och mer. Detta är just nu vad som nu håller på att hända – lösningarna håller som bäst på att bli möjliga att plocka ihop utan att man behöver utveckla stora och komplexa egna komponenter. Egna/proprietära icke-triviala lösningar och komponenter innebär såväl en initial utvecklings- eller införandekostnad som en underhållskostnad, varför nyttan av dessa måste vara mycket högre än den primitivare standardlösningar kan erbjuda.

De flesta mekanismer och behov jag beskriver här bör vara oberoende av plattform, språk och ramverk, men jag riktar in mig på att exemplifiera ett Java-baserat webbsystem då det är min främsta erfarenhet.

Byggprocedur

Systemet ska vara bygg- och körbart från källkod från kommandoraden, helst med ett kommando och med så få beroenden som möjligt till externa verktyg och konfigurationer. Kommandot mvn install är ett bra exempel.

Under utveckling bygger man systemet från en IDE också, men man vill inte vara tvungen att köra en viss IDE av en viss version och förlita sig på dess byggkonfiguration. Jag brukar undvika att ens committa IDE-specifika filer.

I den bästa av världar checkar man ut och bygger en ny arbetskopia av ett system med ett eller kanske två kommandon på en i övrigt ren maskin, bara genom att ange URL till källkodsrepot. För ett java-baserat system krävs kanske också att man först installerar JDK, Maven, Git eller Subversion, kanske också skruva på MAVEN_OPTS för att sätta heap size eller något annat. Ju färre och enklare moment i den initiala setupen, desto bättre.

Automatiska integrationstester

Med integrationstester avses tester som kör hela systemet som det är tänkt att köras i målmiljön, men med eventuella externa beroenden och system mockade (simulerade). Om systemet är en servlet-baserad webbapplikation så deployas applikationen på till exempel Tomcat och testerna använder applikationens externa tjänstegränssnitt (REST, SOAP, RMI etc.) och verifierar dels svaren på anropen och dels sidoeffekter i databaser och i mockar för externa system.

Dessa tester ska vara körbara från kommandoprompten (och naturligtvis även från din favorit-IDE), och kunna köras på lokalt modifierad, dvs ännu ej committad, kod för att kunna stödja en testdriven utvecklingsprocess. Den vanliga och enkla lösningen på detta innebär att man ser till att kunna köra åtminstone de funktionella acceptanstesterna på hela systemet med databaser och mockar för externa system lokalt på utvecklingsmaskinen.

Integrationstesterna består normalt i funktionella acceptanstester (med acceptanstester menar jag här tester som utgår från att testa kravuppfyllnad), implementerade kanske i SoapUI Pro, eller i Java eller Groovy i form av en Unit-test. Det är inte ovanligt att också komplettera med icke-funktionella tester, till exempel kapacitetstest eller mer tekniska tester som till exempel minnesläckagetest. Tester som testar GUI med till exempel ramverk som Selenium ingår också i denna kategori.

Olika klasser av tester görs körbara var och en för sig till exempel genom att gruppera dem i olika moduler.

Testramverket inkluderar initiering och laddning av eventuella databaser och initiering av mockar före exekveringen av varje testfall.

Byggserver

En byggserver lyssnar på commits och bygger då systemet och kör alla tester. De byggservrar jag ser är Jenkins, Go, Bamboo och Teamcity.

Eftersom vissa klasser av framför allt integrationstester kan ta ansenlig tid att köra är det praktiskt att dela upp dem i olika jobb, till exempel

  • Build
  • Integration test
  • GUI test
  • Load test

Enhetstest och kodmetrics

Utvecklingsmiljön måste ha ett bra stöd för enhetstester samt ett sätt att mäta olika aspekter av systemet som enhetstestkodtäckning, komplexitet, API-dokumentation, duplicerad kod m.m., där enhetstesttäckning är det jag väljer som viktigast att arbeta mot. 80% är en miniminivå jag strävar efter.

Rent mentalt blandar jag inte ihop enhetstest med test. Enhetstest är en metod utvecklare använder för att säkerställa att koden gör det utvecklaren avser att den ska göra och att den fortsätter att göra det när vi ändrar den. Men det är inte alls säkert att det utvecklaren avsåg är den funktion som uppfyller kraven, detta adresseras av test.

Enhetsteststödet i Java-baserade system med Maven, JUnit, Jenkins m.m. är moget och omfattande. För Javascript börjar det komma i och med ramverk som Jasmine, Karma, Protractor, m.m. och med ramverk som AngularJS, vilka separerar vy och kontrollogik, så blir enhetstestning av Javascript-logik genomförbart.

Automatisk versionsnumrering

Varje gång systemet byggs skapas dess versionsidentifikation. Den består kanske av ett enkelt bygglöpnummer eller ett versionsnummer enligt Semantic Versioning, på formen major.minor.buildNumber. Det är praktiskt att göra versionen tillgänglig på flera sätt:

  • Inbäddat i själva artefakten, till exempel i war-filens MANIFEST.MF
  • Mavens pom-versioner
  • I runtime i externa API-er. Ett REST-api kan till exempel ha en resurs /applicationVersion
  • I GUI

Versionen taggas automatiskt i kodrepositoriet.

En skarp version av systemet låter man lämpligen byggservern bygga, antingen som resultat av varje incheckning eller med ett speciellt jobb “build-release”.

Man vill kunna skilja på en lokalt byggt version av systemet och en “officiell version” byggt av byggservern. Byggproceduren ska med rimlig säkerhet kunna garantera att en viss version (som inte är av snapshot-typ) inte finns i olika inkarnationer med olika programkod.

Miljödetektering

Systemet ska själv kunna detektera i vilken miljö det kör: Till exempel lokal utvecklingsmiljö, integrationstestmiljö eller produktionsmiljö.

Ett enkelt sätt att göra detta är att sätta en miljövariabel, eller kontrollera hostnamn.

Miljökonfiguration

Den artefakt som byggs ska vara samma för alla miljöer, man vill inte bygga med olika profiler eller liknande mekanism, varför den måste innehålla alla parametrar för alla olika körmiljöer. Här avses parametrar som URL-er för externa tjänster, databaser, portnummer.

Man kan med rätta tycka att det är fel att lägga kunskap om körmiljöer i varje artefakt som ska köra i dem, och det är korrekt invändning i det generella fallet, men denna metod är rätt just i en Continuous Delivery Pipeline, där körmiljöerna i allra högsta grad är kända och man inte har något behov av att kunna ändra miljöparametrar dynamiskt utan att bygga en ny artefakt, eftersom hela poängen med en build pipeline är att det är enkelt att ändra konfigurationen i källkoden och trycka på release-knappen igen.

Feature toggles

Eftersom man utvecklar kontinuerligt i mainline, måste det till ett sätt att slå på och av funktioner, eller hela delar av systemet, snabbt och smidigt på ett enda ställe.

Ett enkelt och ändamålsenligt sätt att hantera feature-toggles på är att införa dessa toggles som parametrar i miljökonfigurationen. Något behov att slå på och av saker i runtime, och faktiskt heller inte ens utan att behöva bygga om artefakten, finns sällan behov av eftersom det i en Build Pipeline är enkelt att ändra i konfigurationen och trycka på release-knappen igen. Då får man dessutom spårbarhet även för ändringar av feature toggles genom sitt versionshanteringssystem.

Databashantering

System som omfattar databaser måste ha ett stöd för att skapa databaser som inte finns, ladda testdata inför varje testfallskörning och att migrera data och schema mellan olika versioner. När applikationen startar upp detekterar den vilken databas den har att göra med och uppdaterar schemat eller konverterar datat. Samma script eller kod används för att initiera och migrera databaser hela vägen från den lokala utvecklingsmiljön till produktionssystemet. På så vis kommer scripten att testas av kontinuerligt.

Det finns några olika verktyg för att stödja detta, till exempel DbDeploy, Liquibase och Flyway DB.

Maskinkonfigurationer

Även maskinkonfigurationen är en del av körmiljön och versionshanteras och automatiseras. Här avses till exempel mängd fysiskt minne, antal CPU-er, paketinstallationer, OS-konfigurationer m.m.

Dessa aspekter hanterar man enklast med verktyg som Vagrant, Puppet, Chef och Docker. Konfigurationsfilerna för dessa lägger man incheckade tillsammans med källkoden, vilket innebär att om man till exempel kommer fram till att man behöver mer fysiskt minne på servern så ändrar man till exempel i Vagrantfile och committar, så fixar din build pipeline resten: En ny maskinavbildning byggs ihop med de nya parametrarna, den gamla maskinen tas ned och den nya med mer minne startas. Inga manuella handgrepp här utan allt är automatiserat.

Releasedokumentation

Det finns flera sätt att hantera spårbarheten, dvs vilka buggfixar och nya funktioner som ingår i vilka byggen.

Den enklaste formen är att förlita sig på version history i systemets källkod. Ett mer ambitiöst och mer lättläst sätt att presentera releaseinformation är i form av release notes. Denna information kan med fördel genereras direkt från ett ärendehanteringssystem som Jira. Det förutsätter att man har disciplin att uppdatera ärenden med adekvat information för buggfixar, features och stories.

Release och deploy

Proceduren att bygga en release-version ska vara antingen helt automatisk, dvs byggservern bygger en releasekandidat så fort något committas, eller så bygger man en release med en explicit knapptryckning, till exempel via ett jobb “build-release” på byggservern.

Proceduren för att releasa systemet till testmiljö eller produktion ska vara i stil med att trycka på en knapp. En lösning är att skapa ett byggserverjobb “deploy-release” där man anger två parametrar:

  • Var systemet ska deployas (kanske en dropdown med “Test” och “Produktion”)
  • Vilken version man vill deploya

Lösningen kan också omfatta steg för manuell och utforskande testning.

Lyxmodellen av build pipeline visualiserar alla bygg och teststeg för alla versioner av systemet i ett verktyg, med koppling till ärendehantering och release-notes. Men allt det behövs inte för att man ska uppnå nollnivån som beskrivs här.

Summering

I den bästa av världar så borde det fungera så att man dag noll börjar med att stansa ut ett system och en Build Pipeline från en lämplig mall som passar in i övrig miljö och övriga rutiner, så att man kan börja producera kundnytta direkt.

Detta kommer förmodligen att vara möjligt så småningom. Idag måste man emellertid plocka ihop en egen lösning varenda gång utvecklingen av ett nytt system startar eller stegvis utveckla den vartefter. Eller alternativt ringa en vän som har gjort det förr.

IT Consultant at CAG Edge. Cloud and Continuous Delivery specialist, software developer and architect, Node.js, Java.

Publicerad i Continuous Delivery, DevOps, Java, Test

Kategorier

LinkedIn Auto Publish Powered By : XYZScripts.com