Pod-tips om DevOps, CD och DoD

Häromdagen lyssnade jag på ett avsnitt av .NET Rocks!, avsnitt 1581.

I det avsnittet samtalade man med Donovan Brown som jobbar i en DevOps-gruppering som inkluderar ett antal verktyg, bland annat VSTS eller Azure DevOps som det heter numera. Samtalet handlade bland annat om Continuous Deployment (CD) av VSTS, med hjälp av VSTS. Det känns spontant som en rejäl utmaning att driftsätta ett system med hjälp av systemet själv. Jag fastnade för ett par grejor i programmet, som det eventuellt kommer att bloggas mer om senare här på Headlight-bloggen.

  • För att leverera kontinuerligt (CD) måste koden klara det
  • Definition of Done, DoD, när är en funktion klar egentligen?

Innehållet under nedanstående rubriker är mina egna tankar och funderingar, det är bara ämnena som kommer från podden.

Fördelar och nackdelar med kontinuerlig leverans utelämnas helt, men min personliga åsikt är att ju oftare man levererar det lättare blir det att göra det.

Är ditt system designat för CD?

När man tänker på begreppet Continuous Depolyment, CD, tänker man nästan alltid på mycket hippare applikationer än vad man själv jobbar med. Det är nästan uteslutande webbapplikationer som levereras på detta sättet, av den enkla anledningen att man bestämmer centralt vilken version av systemet man vill att användarna ska nyttja. Detta är såklart inte alls lika enkelt i fallet "fet klientapplikation".

Hur som helst så är det inte bara typen av applikation eller sättet som applikationen distribueras som påverkar möjligheterna till kontinuerlig leverans. Det är även systemets design. Designen spelar in på alla nivåer, allt från hög nivå, på vilket sätt komponenter, tjänster och funktioner interagerar och integrerar med externa dito hela vägen ner till strukturen på koden, abstraktion, återanvändning av kod etc. Det allra viktigaste här är såklart att ha stabila kontrakt för dom gränsytorna som finns. Av samma anledning, dvs kontrakten för gränsytorna, spelar designen på systemets interna integration stor roll. Om vi tittar på en lägre nivå, på kodnivå, sättet som funktioner och integrationer är implementerade, så spelar dessa såklart också en stor roll. Det är designen på koden och det som påverkar koden som jag tänker filosofera kring när det gäller "redo för CD eller inte".

Automatiserad regressionstestning

Ett absolut tvång för att lyckas leverera kontinuerligt är att ha minst enhetstestning av koden på plats. Önskvärt är att testningen är helt automatiserad och genomförs som en del av leveransprocessen, på t.ex. TFS eller annan miljö för CI/CD.

Varje kodförändring måste täckas av testning. Finns det möjligheter att bygga integrationstester och automatisera dessa så är det önskvärt. Nu kan man börja diskutera huruvida man ska behålla alla testfall över tif, även om man kanske täcker funktioner och kodblock flera gånger, dvs det finns en redundans i testningen. Det finns olika skolor här men jag personligen är för att behålla tester, även om dom är redundanta. Den enkla förklaringen till det här är tvådelad, dels skadar det inte att alla tester finns kvar så länge dom är gröna och fungera, dels så är det inte helt enkelt att välja vilka tester som eventuellt skulle plockas bort. Det är lätt hänt att man plockar bort tester av extremvärden eller specialfall, speciellt om namngivningen av testerna inte är helt klockren. Skulle man plocka bort "fel tester" så är det bara en tidsfråga innan något fel skulle slinka förbi testningen och komma ut i en leverans.

Generellt kan man säga att avsaknad av automatiserade tester och för låg testtäckning, låt säga mindre än 50%, omöjliggör en stabil kontinuerlig leverans.

Önskvärt är också något som jag väljer att kalla sanitetskontroller. Sanitetskontroller är nog inget allmänt vedertaget begrepp men definitionen bör ligga ganska nära "testning där systemet används på ett sätt som en slutanvändare skulle använda det". Hit hör UI-tester och programmerade api-anrop etc, dvs systemet är en stor svart låda där man kan peta enbart på dess yttersta gränsytor. Sanitetskontroller är ett ämne som absolut är värt en egen post eller serie av poster här.

Avgränsning av förändringar

I samband med funktionsändring, utökning eller rättning av fel är det viktigt att påverkan på omkringliggande delar i systemet är minimal. Man kan se det här som ett mått på hur robust systemet är när det gäller design av kod, som är en direkt avspegling av valt designmönster.

Om vi fokuserar just på avgränsning så vill man i samband med förändringar av något slag, påverka enbart den specifika funktionen som man förändrar och inte något annat. Antag att man bygger ett api för något system att konsumera. Om man ska lägga till en funktion i api:et så vill man oftast inte att existerande funktioner ska förändras eller gå sönder. Man vill kunna garantera att kodändringarna endast påverkar den funktionen man vill påverka. Det allra bästa sättet att se till att förändringen är avgränsad är att valt designmönster ger stöd för detta. Ett alldeles lysande mönster då är CQRS, som alltid har helt åtskillda läs- och skrivkomponenter. Med fördel bygger man respektive funktion som helt egen väg igenom koden, egna queries med tillhörande handlers och detsamma på commandsidan med egna commandhandlers. Gemensamt kan vara den lägsta nivån i datacesslagret, som man kan bygga som ett helt generellt CRUD-lager.

Vill man generalisera begreppet avgränsning i kod och inte knyta det till något specifikt designmönster så kan man göra det tämligen enkelt:

Försök att ha helt separata klasser eller funktioner i alla lager för varje funktion eller operation som påverkar systemets domänobjekt.

Att applicera den här filosofin handlar om implementationsdetaljer och påverkar inte någon annan än utvecklarna.

Förändringars avgränsning

Lika viktigt som en bra avgränsning i kod är avgränsningen av själva förändringen av funktionsspecfikationen, dvs ändringen av kravbilden på systemet. Önskvärt är att den kompletta förändringen är iterativt levererbar. I den absolut bästa av världar skulle man kunna gömma den bakom en feature toggle eller feature flag och låta en begränsad, kontrollerad skara slutanvändare utnyttja förändringen eller funktionen. På så sätt skulle systemets robusthet inte äventyras. Feature toggling ställer höga krav på koddesignen och är inte alls avgörande för själva förändringens avgränsning.

Generellt kan sägas att en bra avgränsning av förändringen på kravet eller kraven lägger en bra grund både vad gäller avgränsningen på kodförändringen och testningen. Bra avgränsning här ger en hög konfidens i regressionstestningen av systemet.

Har du några erfarenheter i ämnet kontinuerlig leverans? Kanske har resan från leveranser var 6:e månad till leverans varje natt alltid varit en dans på rosor eller har det gjort ont? Håller koden bättre struktur nu eller var det bättre innan CD?

Definition of Done

Det här är en filosofisk diskussion kring DoD, Definition of Done. Beroende på vilken roll man har i ett utvecklingsteam så har man oftast olika syn på när en funktion är klar. En mindre, kanske helt ogrundad, spaning är att man numera flyttar fram tidpunkten för definition of done. I podden sa man att definition of done borde vara tidpunkten när man får in mätdata, aka metrics, på att funktionen används och inte genererar fel.

Jag tycker det är en spännande syn på att vara klar med en funktion som involverar många fler led i processen för utveckling.

Har man byggt en funktion som inte används eller är så pass svår att använda så att den genererar en massa fel, kanske hittar man inte ens fram till den, kan man då säga att man är klar?

Att flytta fram tidpunkten för en funktions klarmarkering till tidpunkten när man fått in metrics på att den nyttjas och fungerar inbjuder också till att man bör implementera metrics i sitt system så tidigt man bara kan. I tidigare inlägg här på bloggen utmanades införandet av IoC-ramverk i projekt. Tänk om man kunde byta plats på ramverk för IoC och ramverk för metrics?

Det skulle vara en dröm, att få komma in i ett projekt som levt ett tag där man inte har ett IoC-ramverk men istället ett ramverk för metrics och till det ett tydligt grafiskt gränssnitt för att visualisera dessa data.

Tveka inte att utmana och tyck till om definition of done. När tycker du en funktion är klar egentligen?