Hvordan sikre en god løsningsarkitektur?
Etter innledende diskusjoner i Novanet ble vi enige om at alle prosjekter er forskjellige, og at det er vanskelig å sette opp en standard arkitektur som vil dekke alles behov. Noen har behov for høy ytelse, noen må fokusere 100% på sikkerhet og andre ønsker å kunne levere funksjonalitet raskt. I de aller fleste prosjekter ønsker man en god kombinasjon av disse. Dette gjorde at vi konkluderte med at vi skulle kartlegge de viktigste arkitekturprinsippene som vi bruker i prosjektene våre. Dette for å gjøre konsulentene i Novanet mer bevisst på disse når man er i prosjekt, og for å kunne dele på denne kunnskapen med kundene våre.Vi fikk spørsmål om vi kunne beskrive hvordan vi går frem for å sikre god løsningsarkitektur, og da var diskusjonen i gang. Kan en gjenbruke den samme arkitekturen på tvers av løsninger, bransjer, organisasjoner og teknologier? Er det mulig å lage noen generelle prinsipper for arkitektur?
Arbeid med prinsippene startet i Novanets faggruppe, bestående av Lars Alexander, Jan Tore og Leif. De kom opp med forslag til noen viktige prinsipper og hvordan disse kunne struktureres. Dette ble presentert for resten av Novanet på en av våre fagkvelder, hvor alle samarbeidet i Miro med å komme opp med flere prinsipper og punkter for hver av disse som burde beskrives. Etter dette startet skrivearbeidet, hvor hvert prinsipp ble beskrevet med underpunkter, kilder, diagrammer osv. Alt ble samlet i en intern Wiki hvor en kan utforske hvert prinsipp. Arbeid med prinsippene vil være en kontinuerlig prosess for å holde den oppdatert.Hvordan jobbet vi?
Her er et utvalg av noen av prinsippene som er beskrevet: Det er åpenbart at sikkerhet er viktig når en skal bygge tjenester som har til hensikt å dele data og forretningslogikk. For dette prinsippet har vi beskrevet grunnleggende begreper som autentisering og autorisasjon, men også prøvd å forklare en del rundt autentiseringsflyter, protokoller, mulige risker (OWASP Top 10) osv. Brukere foretrekker tilgjengelighet fremfor konsistens. For eksempel så kan brukeren godta at lagerstatus er feil eller at det mangler produktbilde, så lenge de kan gjøre en bestilling. For å oppnå dette, bør hver tjeneste kunne levere sin funksjonalitet uten avhengigheter på andre tjenester. F.eks. at tjenesten for bestilling fungerer selv om tjenesten for lagerstatus er nede. Dette prinsippet er tett knyttet til det forrige prinsippet. For å bygge tjenester med minst mulig avhengigheter til hverandre, så bør en vurdere asynkron kommunikasjon mellom disse. Dvs. at en tjeneste ikke gjør direkte kall og venter på svar når den trenger data fra en annen tjeneste. I stedet brukes en meldingsbuss eller lignende, som gjør at tjenestene kan sende ut en melding om en hendelse/endring som har inntruffet (f.eks. at en kunde har endret adresse), så kan de andre tjenestene reagere på denne hendelsen (f.eks. oppdatere adressen i sin database). Da kan tjenestene levere data eller funksjonalitet som baserer seg på data i egen lagring/database uten å være avhengig av andre. Når en utvikler skriver kode bør relatert funksjonalitet isoleres i moduler, og den interne funksjonaliteten i en modul bør kun gjøres tilgjengelig via en definert kontrakt (interface). På denne måten sørger en for at det ikke gjøres funksjonskall på kryss og tvers av koden, og ender opp med "spagettikode". Dette prinsippet kan overføres til utvikling av tjenester, og spesielt i forbindelse med mikrotjenester. En tjeneste bør ha ansvar for et avgrenset sett med funksjonalitet, og ikke vokse ut over dette ved å inkludere funksjonalitet som ikke er relatert. Men å avgjøre hva som er relatert funksjonalitet, og når en bør legge funksjonalitet i en ny tjeneste kan være vanskelig. For dette prinsippet har vi skrevet om Event Storming, Domain Driven Design og andre teknikker for å komme frem til riktig størrelse og ansvar for en tjeneste. Når en starter å bygge komponenter og tjenester, og ikke har så mange av disse, så er det enkelt å driftsette og ha kontroll. Men når systemet vokser og man får flere tjenester og komponenter, blir dette verre. Derfor er det viktig at man tar stilling til dette tidlig, og velger en arkitektur som legger til rette for enkel driftsettelse og kontroll. Arkitekturen må legge opp til at hver tjeneste/komponent kan driftsettes hver for seg. Videre må en se på automatisering; Automatisering av infrastruktur (Infrastructure-as-code med f.eks. Terraform, Azure CLI eller Bicep), automatisering av driftsettelse (bygg og deploy med f.eks. Azure Devops eller TeamCity) og annen automatisering som f.eks. automatisert bytte av passord og API-nøkler (key rotation). For komplekse systemer med høye krav til fleksibilitet og skalering bør kontainerteknologi (som Docker) og Kubernetes vurderes. Du kan lese mer om dette i vår artikkel om Devops. På samme måte som en ønsker å kunne driftsette tjenester/komponenter uavhengig av hverandre, bør arkitekturen legge til rette for at disse kan skaleres uavhengig av hverandre. Funksjonalitet som ligger i en tjeneste, kan ha et helt annet behov for ytelse enn en annen. Kanskje finnes en tjeneste som brukes av firmaets nettside hvor det er mange besøkende, mens en annen tjeneste som er koblet mot et administrasjonsgrensesnitt som bare brukes intern. Disse vil sannsynligvis ha forskjellige behov for ytelse. Det samme prinsippet gjelder for infrastruktur, som en database. Hvis all data ligger i samme database, vil denne måtte skaleres ihht. ytelsesbehovet på tvers av tjenestene som benytter den. Mens hvis hver tjeneste har egen lagring/database, kan hver at disse spesifiseres/skaleres separat. Å bygge en arkitektur som forholder seg til dette prinsippet gir bedre fleksibilitet ift. skalering, og flere muligheter til å påvirke ressursbruk og kostnader. Utover disse har vi samlet et titalls andre større og mindre prinsipper som utgjør et godt grunnlag for å ta beslutninger i samarbeid med våre kunder for å utarbeide en løsningsarkitektur som leverer på de kravene som stilles.Hva ble resultatet?
På den interne Wiki-siden listes de prinsippene vi anser som viktigst. Dette kan f.eks. være prinsippet «Bygg sikre tjenester». Selve prinsippet beskrives, i tillegg til konsepter som f.eks. autentisering og autorisasjon. Så kan en klikke seg videre til alternativer for implementasjon av dette prinsippet, som protokoller, tokens, claims osv. For hvert at disse alternativene er det mulig å klikke seg videre til teknologi som er mulig å benytte for implementasjon, som Azure AD, Identity Server eller ASP.NET Identity. Disse blir beskrevet med fordeler, ulemper og når det passer å bruke disse.Bygg sikre tjenester
Preferer tilgjengelighet fremfor konsistens
Vurder asynkron kommunikasjon mellom tjenester
Sørg for at tjenestene har riktig størrelse og ansvar
Design for enkel driftsettelse
Gjør det mulig å skalere tjenestene
I bruk
Vi jobber fortsatt med å skrive detaljert om alle prinsippene. Dette er nyttig og lærerikt for alle involverte, enten en får repetert kunnskap man allerede har eller lærer noe nytt. Ved å få på plass denne oversikten håper vi at våre konsulenter får et mer bevisst forhold til prinsipper som gjelder ved utvikling av tjenester. I tillegg vil de gjøre det enklere for nye ansatte å forstå hvordan vi jobber med løsningsarkitektur og tjenester.
Trenger du hjelp til å sette opp en arkitektur basert på erfaring og kjente prinsipper? Eller ønsker du å jobbe med en gjeng som er opptatt av dette?
Ta kontakt med
Lars Alexander Jakobsen
laj@novanet.no
414 71 717