Denne medarbejderblog er skrevet af Carsten Agger, softwarearkitekt og teknisk projektleder hos Magenta Grønland.
I Magenta Grønland udvikler og drifter vi et stort antal fagsystemer for det grønlandske selvstyre. For at kunne gøre dette, prøver vi at gøre de enkelte systemer så ensartede som muligt, så vi kan koncentrere os om at forstå de forretningsprocesser, som systemerne skal implementere.
For at opnå dette bygger vi alle nye systemer med den samme teknologistak – med det populære Python-framework Django og deployment på Linux-servere med Salt og Docker og et fælles overvågningssystem baseret på Prometheus og Grafana.
Derudover bestræber vi os på et følge nogle enkle principper og best practices for selve udviklingen, som vi vil skitsere herunder.
Udviklingsmetodologi
Agile
Magenta Grønland følger overordnet set filosofien bag Agile Development som udtrykt ved Manifesto for Agile Software Development. Manifestet er kort nok, til at det er værd at citere i sin helhed:
We are uncovering better ways of developing software by doing it and helping others do it.
Through this work we have come to value:
- Individuals and interactions over processes and tools
- Working software over comprehensive documentation
- Customer collaboration over contract negotiation
- Responding to change over following a plan
That is, while there is value in the items on the right, we value the items on the left more.
En anden måde at sige det på er, at vi koncentrerer os om at lave den software som kunderne ønsker. Det vil sige: Giver den funktionalitet og værdi (“working software”), som kunden gerne vil have. Dette afklarer vi løbende i dialog med de mennesker, der faktisk skal bruge vores software (“individuals and interactions”, “customer collaboration”) – og vi holder selve arbejdsprocessen så “letvægts” som muligt, så vi let kan rette ind (“responding to change”), hvis kunden har nye ønsker eller vi selv har misforstået noget.
Denne måde at arbejde på, og dette fokus, giver en mere fleksibel arbejdsproces og et produkt, der er tættere på brugernes ønsker, end den mere traditionelle “vandfaldsmodel”, som manifestet bl.a. er en reaktion på.
Fordele ved den agile tilgang
Tingene bliver lavet hurtigere, billigere, bedre og mere koordineret med brugernes ønsker, end de ellers ville.
Ulemper ved den agile tilgang
Agil udvikling kræver, at de mennesker, der arbejder med systemet til daglig, selv kan tage beslutninger om at skifte retning, hvis det er nødvendigt. Denne arbejdsform kræver dermed en rimeligt selvkørende (og gerne erfaren) gruppe af udviklere. Den agile tilgang er ikke noget, man bare kan “lægge på” i en stor organisation, hvor folk er vant til at følge andres beslutninger uden at sætte spørgsmålstegn ved dem.
Der kræves altså en hel del selvdisciplin og et fast øje på projektets retning i det agile team.
Domain Driven Design – en dyb forståelse af forretningsdomænet
Begrebet Domain Driven Design (DDD) blev indført i 2003 i Eric Evans’ bog af samme navn, men selve ideen går længere tilbage og er en af grundpillerne i objekt-orienteret design.
Tanken er, at man bygger en model for det kommende systems forretningsdomæne ved hjælp af de samme begreber, som områdets udøvere bruger i deres daglige arbejde. De mennesker, der skal udarbejde softwaresystemet – analytikere, arkitekter, designere og udvikler – kommer dermed til at ræsonnere om de ting, som systemet skal kunne, i det samme sprog som dets brugere vil anvende, når de diskuterer området med hinanden.
Denne model får dermed karakter af et fælles sprog, så de involverede på forskellige niveauer så vidt muligt kan bruge de samme ord og begreber til at tale om, hvad softwaren skal gøre.
DDD har den fordel som metode, at den indkoder en dyb forståelse af forretningsområdet i selve programmets struktur og datamodel. En sådan forståelse er alligevel nødvendig, eftersom systemet og dets måde at virke på kommer til at definere forretningsområdet for dets daglige brugere.
Det fælles sprog går igen på alle niveauer i det færdige system, og derfor skal udviklere og arkitekter hos leverandøren ikke bruge mental energi på at oversætte fra brugernes begrebsverden til deres egen. Det kan ellers let ske, hvis systemets datamodel ikke passer til forretningsområdets begrebsverden.
Fordele ved Domain Driven Design
Brugere og udviklere kan tale om, hvad programmet skal kunne, i det samme sprog, og der skal ikke bruges nær så mange kostbare ressourcer (mental energi eller mellemled) til at “oversætte” mellem det forretningsmæssige og det tekniske.
Ulemper ved Domain Driven Design
Det kan tage tid og mange iterationer at nå frem til en objektmodel og et fælles sprog, der giver en god repræsentation af forretningsområdet. Dette kan især stille store krav til de project managers og arkitekter hos leverandøren, der skal opbygge modellen.
MVP - evolutionary delivery
Et vigtigt princip i Magenta Grønlands tilgang til systemudvikling er evolutionary delivery med udgangspunkt i det mindst mulige gangbare produkt eller “Minimal Viable Product” (MVP), som det hedder på engelsk.
Det er et iterativt princip og repræsenterer samtidig nogle fravalg:
- Vi går efter at lave den rigtige objektmodel og det rigtige produktionssystem fra starten og laver derfor ikke prototyper, der smides væk bagefter.
- Vi forsøger ikke at udtænke hele systemets endelige funktionalitet fra starten i en “vandfaldsproces”, men går efter at lave noget, som kunden kan komme i produktion med så hurtigt som muligt.
Prototyper er fravalgt, fordi det er vores opfattelse, at et moderne RAD-værktøj som Django fint understøtter, at man laver tingene rigtigt fra starten – samtidig med, at det er let at lave refactoring og rette ind, når man bliver klogere på brugernes virkelighed.
“Vandfaldsmodellen” er fravalgt, fordi et helt nyt system i et komplekst problemområde som offentlig administration altid viser sig at skulle fungere anderledes, end nogen – brugere, projektledere, systemdesignere og -udviklere – er i stand til at forudsige fra starten. Ting, der forekom meget vigtige, før systemet kom i brug, bliver sekundære – og nye behov, som der ikke var tænkt på i forvejen, opstår.
Fordele ved evolutionary delivery
MVP-tankegangen sikrer, at vi i hver enkelt fase fokuserer på at lave netop det, som giver mest værdi i kundens daglige drift. Den er således også et godt værn mod “scope creep” – vi fokuserer på at lave noget, som kunderne kan komme i gang med at bruge så hurtigt som muligt og hjælper os til at skære ønsker med lavere prioritet (“nice-to-haves”) fra.
Ulemper ved evolutionary delivery
Det er vigtigt, at der i forbindelse med de enkelte faser i produktudviklingen sker en refactoring for at sikre, at systemet stadig er struktureret på en måde, der er let for andre udviklere at forstå og vedligeholde. Hvis man ikke er opmærksom på dette og ikke indregner det i sine estimater, risikerer man at skabe et system præget af teknisk gæld.
Den store omstillingsparathed stiller derfor store krav til udviklere såvel som projektledere.
Løbende dialog med kunden
Vi bestræber os på altid at involvere kunden i den løbende udvikling og indsamle feedback så tidligt i processen som muligt – ideelt set lige så snart, vi har noget, som det er muligt at teste. Dette er vigtigt for at sikre kundens ejerskab af hele udviklingsprocessen; ikke mindst for at sikre, at det færdige produkt netop er, hvad der skal til for at understøtte kundens arbejdsgange.
Fordele ved løbende dialog med kunden
En nær dialog med kunden sikrer, at vores projektledere og udviklere får en god forståelse af kundens behov og krav til systemet.
Ulemper ved løbende dialog med kunden
Det kræver, at kunden afsætter ressourcer i form af en kontaktperson med et godt fagligt overblik over systemets domæne (ideelt set en af de fremtidige brugere). Dette er ikke altid muligt.
Versionsstyring og Continuous Integration
Versionsstyring
I det daglige arbejde bruger vi Git til versionsstyring af koden. Ny funktionalitet udvikles i et separat spor, der ikke kan forstyrre de dele af systemet, der allerede er godkendt. Et sådant separat udviklingsspor kaldes i Git en branch, og de allerede godkendte dele af systemet ligger i en særlig branch, der kaldes master. Når ny kode, som er introduceret i en branch, kan godkendes og alle automatiske tests kan afvikles uden fejl, kan den flettes ind i master-branchen.
Continuous Integration
Vi anvender Continuous Integration (CI) i udviklingsprocessen. Rent praktisk vil det sige, at hver gang en ny funktionalitet eller en fejlrettelse integreres i det færdige system, kører der en proces – en såkaldt pipeline – der helt automatisk bygger hele systemet og kører en række tests på det. Det er kun tilladt at lægge ny funktionalitet ind i systemet, hvis systemet kan bygges og alle disse tests kan udføres med succes (vi siger, at pipelinen består).
Automatisk deployment til test
Systemet er sat op med en testserver og en produktionsserver, der kører i så ens omgivelser som muligt.
Når en ny funktionalitet eller en fejlrettelse flettes til master-branchen, genereres der automatisk en “release candidate”-verion, og denne rulles ud til testserveren.
Der kan rulles ud til produktionsserveren, når ændringen er testet og godkendt på testserveren.
Containerbaseret installation
De forskellige versioner af systemet bygges i CI-pipelinen. De bygges som Docker-containere, som kan køre som en slags meget begrænsede virtuelle maskiner på en server.
Selve installationen består i, at serveren henter det nyeste Docker-image og kombinerer det med den nyeste opsætning af omgivelserne (fil-stier og passwords og den slags), der i tilfælde af “secrets” dekrypteres før installationen.
Kodereview
For at ny kode kan integreres i det færdige system, skal den bestå alle automatiske tests, men dette er ikke nok – den skal også godkendes af en af projektets andre udviklere. I praksis foregår dette ved, at der i Magentas Gitlab-system oprettes en merge request (“fletteanmodning”), hvor det er muligt at få en simpel grafisk oversigt over ændringerne i koden og se, om de automatiske kvalitetstests er bestået. Denne merge request kan nu tildeles en af de andre udviklere på projektet. Denne vil typisk hente ændringen ned på sin egen computer og teste den by funktionalitet dér og vil også læse kodeændringerne igennem for at se, om der er nogen problemer, der springer i øjnene. I tilfælde af problemer vil revieweren påpege dem, og udvikleren kan så ændre sin foreslåede kode, indtil udvikler og reviewer er enige om, at ændringen kan tilføjes systemet.
Ved mindre, trivielle ændringer (f.eks. af opsætningsparametre) er kodereviewet ikke altid så grundigt (men skal være der), og større eller mere kritiske ændringer kan reviewes af to eller flere personer.
Kvalitetssikring og automatiske tests
I systemets CI-pipeline køres der som sagt en række analyser af tests af systemet, ligesom det verificeres, at hele systemet kan bygges og gøres køreklart.
Statisk kodecheck
Flake 8 - grundlæggende sundhedscheck
Som kontrol af kodens overordnede kvalitet køres analyseværktøjet flake 8
. flake 8
kører nogle grundlæggende kontroller, der sikrer, at
- koden er fri for syntaks-fejl
- koden er formatteret i henhold til PEP8, som er den gældende standard for Python-kode
- koden ikke indeholder navne på variabler og moduler, der ikke er erklæret nogen steder
- variabler, der erklæres samt moduler, der importeres, bliver brugt.
Koden undersøges også for overdreven cyklomatisk kompleksitet og en lang række andre logiske fejl.
Black - formattering
På projekter med kodereview kan forskellige personers holdninger til, hvordan koden skal formatteres for at være så letlæselig som muligt, give anledning til konflikter.
Disse overvejelser gør vi irrelevante ved at formattere Python-koden med formatteringsværktøjet black
. black
formatterer koden, så alle linjer er højest 88 karakterer lange og holder sig ellers inden for rammerne af PEP 8-standarden. CI-pipelinen foretager ikke selv ændringer i kodefilerne, men fejler, hvis en kørsel af black
ville have ændret noget.
Værktøjets navn kommer af Henry Fords: “Any customer can have a car painted any color that he wants so long as it’s black”.
Isort - rækkefølge af imports
Rækkefølgen af import-sætninger i starten af hver Python-fil standardiseres med værktøjet isort
. Denne praksis er overtaget fra Djangos kodestandard.
Automatiske tests
Testdækning - intern og ekstern test
Koden skal testes med unit tests, der simulerer kørslen af de forskellige dele af koden. I forbindelse med test af software opererer man ofte med begreberne intern test og ekstern test, også nogle gange kaldet “white box” henholdsvis “black box”-test.
- En intern test skal sørge for, at alle dele af koden bliver afviklet med forventet resultat, og at alle grene af
if
-statements og lignende bliver afviklet - En ekstern test laves uden noget kendskab til den pågældende kodes interne struktur, men afprøver, om den opfører sig som forventet for forskellige sæt af inputs.
Disse to typer tests kan suppleres med integrationstests eller end-to-end-tests, der afprøver systemet hele vejen igennem.
Automatiske tests i CI-pipelinen supplerer men kan ikke erstatte manuelle tests af ny funktionalitet.
I den automatiske test i vores CI-pipelines har vi som best practice en målsætning om:
- 100% test coverage – alle linjer i koden skal udføres mindst én gang, og alle grene af alle
if
-sætninger og lignende skal rammes - Ekstern test af alle væsentlige use cases og funktioner, så det sikres, at de giver de forventede resultater for realistiske inputs.
Det sidste punkt kan være svært at måle, men det første er let nok og giver et basalt sanity check: Vi kræver, at al Python-kode skal have 100% test-dækning; opnås dette ikke, vil CI-pipelinen fejle, og ændringen kan ikke flettes til det færdige produkt.
REUSE - styr på open source-licenser
Et juridisk set kompliceret aspekt af udvikling af open source-software er at holde styr på de forskellige licenser, der indgår i det færdige produkt. Dette kan automatiseres ved hjælp af værktøjet REUSE.
For at bruge dette skal der tilføjes SPDX-headers til alle filer i kildekoden – disse headers angiver den ønskede licens, for eksempel sådan her:
# SPDX-FileCopyrightText: 2023 Magenta ApS <info@magenta.dk>
#
# SPDX-License-Identifier: MPL-2.0
Det automatiske REUSE-check kontrollerer, at alle filer i kildekoden er dækket af en passende licens og fejler, hvis der ikke er angivet en korrekt licens til f.eks. et tredjeparts-bibliotek, som er inkluderet i koden.
Open source fra starten - automatisk synkronisering med Github
Nye projekter sættes op til automatisk at synkronisere den seneste version af koden (master
branch) til et offentligt repository på Github. På denne måde sikres det lige fra starten, at projektet er 100% open source og koden tilgængelig.