I vår ständiga strävan efter enkelhet och separation befinner vi oss nu i eran av webbtjänster, eller mikrotjänster om man vill ta konceptet till sin extrem.

Vi är på väg att jobba oss bort från de överlagrade och överkomplexa monoliterna från igår, med goda skäl. Men att bygga mikrotjänster är inte heller okomplicerat. I värsta fall får man det sämsta av två världar: en distribuerad monolit, som inte bara är svåröverskådlig för att den är stor och komplex, utan dessutom för att den är fragmenterad och utspridd.

Så det första man bör tänka på är att hålla sina tjänster väl separerade, med litet eller inget beroende till varandra.

Nästa problem blir då att avgöra vad som är en tjänst och vad som snarare bör vara uppdelat i flera tjänster, eller kanske ingå som en del av en tjänst. Det korta svaret är att det beror på, framförallt på hur domänen ser ut och vilka användarfall applikationen ska uppfylla. En hel del klokt har skrivits om detta inom området domändriven design, varför vi inte behöver gå in närmare på det här.

Men anta att vi har hittat ett väl avgränsat ansvar för vår webbtjänst. Hur bör webbtjänsten organiseras för att på bästa sätt uppfylla användarnas behov och vara anpassningsbar efter framtida krav? Det här är ett mindre väl utforskat område. Många projekt-team hittar sin egen ansats, genom en blandning av kunskap, erfarenhet och digert tankearbete. Eller så blir varje tjänst olika, beroende på vem som skriver den eller andra tillfälligheter. Dessutom händer det allt som oftast att en från början välkomponerad webbtjänst degenererar till en big ball of mud efterhand som funktionaliteten utökas och fler kockar blandar sig i soppan.

Att organisera en webbtjänst

För att råda bot på den osäkerhet som ofta råder kring hur en webbtjänst bör utformas och utvecklas har jag kondenserat mina erfarenheter på området till en tydlig modell för webbtjänstutveckling: Mission-Story-Domain-modellen.

Det här är en modell som bygger på S.O.L.I.D-principerna och som kan tillämpas oberoende av domän och teknikval (T ex webbramverk, REST/SOAP, CQRS/synkron m.m.). Men för att hålla det någorlunda konkret antar vi att tjänsten följer REST-protokollet, är synkron (konsumenten väntar på resultat som svar på sina kommandon) och är implementerad som en Microsoft ASP.NET web service ovanpå .Net Core.

Modellen tar sin utgångspunkt i att utveckling av en webbtjänst (liksom i stort sett all annan mjukvaruutveckling) är en inkrementell process: Man börjar litet och lägger till funktionalitet undan för undan. Därför är tiden en integral del av modellen - vartefter som tjänsten växer delas den in i fler moduler med snävare ansvarsområden.

Låt oss därför gå igenom en webbtjänsts anatomi i kronologisk ordning.

Main

Det är här man börjar, i dubbel bemärkelse. Det är den första modul man skapar och dess primära ansvarsområde är att sköta uppstart av webbtjänsten, vilket bland annat innefattar att binda samman tjänstens olika delar med dependency injection. I Main lägger man även global felhantering, autentiseringslogik och liknande aspekter som inte berör tjänstens primära ansvarsområde. Så mycket som möjligt av den generella webbtjänstlogiken paketeras med fördel i externa moduler som kan återanvändas av andra tjänster i lösningen, t ex med Nuget.

Main.Test

Det rekommenderas starkt att man driver fram, eller åtminstone täcker, den funktionalitet man utvecklar med tester. Det gäller även uppstarts-logik. Till exempel är det en god idé att testa att varje controller-klass kan bindas till de service-klasser den använder vid uppstart. Varje annan modul bör på samma sätt åtföljas av motsvarande test-modul, med ".Test" som prefix till namnet.

Story

Antag att vi nu har skapat en fullt funktionell webbtjänst som är sjösatt i en testmiljö och integrerad med de kringtjänster som den behöver för till exempel loggning och versionshantering. Kanske har den en Ping-metod som visar att vi kan anropa tjänsten och få ett svar tillbaka. Innan vi faktiskt börjar implementera användarfall så skapar vi en ny modul (samt motsvarande testprojekt) som är helt oberoende av den infrastruktur som uppstartslogiken kräver. Vi kallar den Story, för det är här våra user stories ska implementeras.

I vårt fall (kom ihåg att vi bygger en REST-tjänst med ASP.NET MVC) består Story-modulen av ett antal service-klasser och tillhörande service-interface som vi kan binda till våra controller-klasser (som fortfarande ligger i Main-projektet).

Nu ser vår tjänstarkitektur ut såhär:

Main > Story

Mission

Efter hand som Story implementerar allt fler användarfall uppstår behovet av interna service-klasser och interna modeller, utöver de som används av våra controllers och som kommuniceras med användaren. Då är det dags att tydliggöra vad som är tjänstens mission: Mer konkret, vilka uppgifter utför den och vilken data tar den emot respektive levererar till sina konsumenter. Till en början kan Mission vara en mapp under Story med alla service-interface och modeller som används av controllers i Main. Men ännu tydligare blir uppdelningen om man lägger dem i en egen modul, som inte behöver vara beroende av något annat i Story.

Vår tjänstarkitektur blir då:

Main > Story > Mission

(Main är även beroende av Mission eftersom den sköter dependency injection mellan Story och Mission)

Front

För att renodla separationen mellan Story och Mission även för kompilatorn, så är det nu dags att bryta ut det som inte är uppstart-logik från Main, det vill säga de controller-klasser som tar emot anrop från tjänstens konsumenter och delegerar till de service-interface som finns i Mission. Controller-klasserna placeras i Front, tillsammans med eventuella attribut och input-modeller som har beroenden till webbramverket (t ex för valideringslogik). Front är alltså, till skillnad från Story, beroende av vilken infrastruktur som används för att kommunicera med användaren. Därför är det en god idé att flytta så mycket logik som möjligt från Front till Story. Då blir det enklare att i framtiden välja en annan webbteknik, eller något annat än webben som överföringsmekanism. Man slipper även dessa beroenden i sina användarfall-tester.

Nu ser vår tjänstarkitektur ut såhär:

Front > Mission < Story

(vi utelämnar Main, som då den sköter dependency injection har beroenden till alla övriga moduler)

Back

De allra flesta webbtjänster behöver anropa andra tjänster i sin tur för att utföra delar av sitt uppdrag, eller har andra beroende som man kan vilja kapsla in (t ex en databas, filsystemet eller systemklockan). I enlighet med dependency inversion-principen i S.O.L.I.D vill man abstrahera bort dessa detaljer från den affärslogik som implementeras av Story. Det uppnår vi genom att lägga dem i en eller flera Back-moduler som beror på Story, eller mer specifikt implementerar klient-interfaces i Story.

Vi får då följande struktur:

Front > Mission < Story << Back

Domain

Nu till den sista av våra tre huvudmoduler. Efterhand som tjänsten växer och tar på sig större ansvar med allt fler användarfall finns risk att Story börjar likna en monolit: Dess ansvarsområde blir för stort och därmed otydligt, givet den detaljrikedom som krävs för att implementera alla användarfall. Det är också möjligt att man än så länge saknar en tydlig modell över den domän man rör sig inom och att tjänsten därför börjar kännas fragmenterad: Man ser inte skogen för alla träd.

Lösningen är att separera logik som berör användarfallen från logik som mer handlar om hur domänen fungerar, oberoende av vad användaren vill utföra inom den. Vi får då en eller flera Domain-moduler som innehåller affärsregler och begrepp inom domänen och kan hålla Story-modulen på en högre abstraktionsnivå som koncentrerar sig kring användarfallen.

En annan fördel med att dela in tjänsten i flera domain-moduler är att varje back-modul då inte behöver känna till alla detaljer om hela domän-kontexten för att uppfylla det ansvar som definieras av dess klient-interface. Den behöver bara ha kännedom om den specifika domain-modul som delegerar till den.

Vi får då en hierarkiskt struktur med en eller flera domain-moduler som var och en delegerar till en eller flera back-moduler, enligt följande:

Front > Mission < Story << Domain << Back

Utility

Den sjunde och sista modulen uppstår ofta under utvecklingens gång, ur behovet att slippa upprepa algoritmer som inte finns tillgängliga i det underliggande ramverket (.Net Core i vårt exempelfall). Dessa samlas lämpligen i ett projekt, Utility, som alla övriga moduler kan referera vid behov. Det är viktigt att Utility är fri från beroenden som kan tynga ner de moduler som refererar till den. Hur benhård man vill vara är en avvägning, men tumregeln är att om en funktion i Utility har ett beroende som inte alla andra moduler redan har, bör den snarare kapslas in i en proxy och tillgängliggöras via dependency inversion för lämplig domän och/eller Story.

Mission-Story-Domain-modellen

Vi har nu steg för steg arbetat oss fram till den fullständiga modellen:

Main
v
Front > Mission < Story << Domain << Back
v
Utility

Ett annat sätt att förstå modellen är att se att var och en av dess delar besvarar en specifik fråga om tjänsten:

  • Front: Vem är jag?
    Svar, t ex: En .Net Core web service med en mission

  • Mission: Varför finns jag?
    Svar, t ex: För att utföra dessa uppgifter åt mina konsumenter

  • Story: Vad gör jag?
    Svar, t ex: Implementerar dessa användarfall

  • Domain: Hur gör jag det?
    Svar, t ex: Genom att modellera domänen i kontexten av min berättelse (story)

  • Back: Var finns mina resurser?
    Svar, t ex: Hos dessa externa tjänster

  • Main: Hur börjar jag?
    Svar, t ex: Genom att binda ihop applikationens olika delar, tillsammans med behörighetskontroll, felhantering och loggning

  • Utility: Hur undviker jag kodduplicering?
    Svar, tex: Genom att samla återanvändbara algoritmer och funktioner så att de är tillgängliga från övriga moduler

När man har kommit så långt att man brutit ner tjänsten i alla sju modul-typer, varav flera domain- och back-moduler, samt tillhörande testklasser, så kan det vara en bra idé att dela in modulerna i tre grupper (solution folders om man använder Visual Studio):

  • Application, som innehåller Main, Front, Mission, Story och Utility (och tillhörande test-projekt)

  • Domain, som innehåller domain-modulerna, samt

  • Back, som innehåller back-modulerna

När även denna struktur börjar kännas för trång är det hög tid att dela upp sin tjänst i flera, lämpligtvis genom att bryta ut någon av de domän-moduler den innehåller till en egen tjänst.

Hoppas ni kan använda den här modellen framgångsrikt i ert projekt. Kommentera gärna nedan om den löste något specifikt problem i projektet, eller om ni var tvungna att frångå den för att uppfylla något särskilt behov.

The computer is the most profound machine that we humans have ever created and ever will create. In the late 1940'ies, it was the newest and most advanced, but not essentially different from the other machines of the machine age, which had started cirka 200 years earlier with the invention of the steem engine. But it would soon come to rule them all and mark the beginning of a new age.

Before 1950, computers were operated, much like other machines, by connecting wires, pressing buttons and pulling levers. But by 1950 the operator no longer controlled the computer harware directly. Instead a new layer of abstraction, called software, had emerged, which was manipulated though special input and output devices. This hardware-software separation is what marks the transition into the new era of computers.

Since then there has been a major breakthrough in our relation with computers aproximately every nine years. I predict that apart from the eight milestones we have already passed, there will be five more, after which the computer age is over.
 
These are the milestones of the computer age:

1950: The human computer interface: The computer age dawns when software is clearly separated from hardware. The computer is operated by manipulating its software and studying its output through a human computer interface - typically consisting of a typewriter, a printer, punchcards and magnetic tapes.
1959: The terminal: The typewriter and printer are combined into an electronic device that can send input directly to the computer and present the output immediately on a screen
1968: The display: The advance of computer graphics makes it possible to display an abstract representation of a program state in both textual and graphical form. The program state can be directly manipulated through input devices such as the newly invented computer mouse.
1977: The personal computer: Computers have become sufficiently small, cheap, powerful and easy to use, that many people want to own a computer. From being exclusive to large coorporations and science labs, it now makes its entrance into regular homes and offices. TV-screen, keyboard and mouse become the standard human-computer interface.
1986: The portable computer: All parts of the computer can now be integrated into a foldable case that can be carried around. You no longer have to sit down by a desk to use a computer.
1995: The connected computer: Both the Internet and the world wide web have existed for a while, but this year it reaches the mass market. The internet boom coincides with the multimedia boom, giving the large public a rich experience of graphics and sound, both online and off-line.
2004: The mobile computer: Smartphones are taking over the mobile phone market and the iPhone is just a couple of years away. Everyone can now have a computer in their pocket.
2013: The ubiqitous computer: The most recent breakthrough, cloud computing, is less appearent for the end user, but it revolutionises creation and hosting of web applications. Most traditionally desktop based applications move to the cloud for a greater collaborative online experience.
2022: The wearable computer: The computer no longer has to be something you carry with you - you weare it; as a watch, glasses, jewelry or even implants. It is powerful enough on its own but can also connect seamlessly with the cloud and devices around it. Screens are cheap and expendable. They can be folded, laser projected or even generated on demand.
2031: The etheral computer: The cloud moves out from the massive data centers and into micro-servers all around us, connected wirelessly with eachother. It is no longer meaningful to talk about data as stored in a specific number of copies at specific physical locations. Instead we measure data in terms of accessibility and integrity.
2040: The neural computer: Naturally we will want to interface with the computer without the bother of having external devices that only can be accessed though sensory input and output. In the beginning of the 2040's, the techology is finaly ready to connect us to the computer directly on the nerves. We will be able to see, hear, smell, taste and feel the computer state and control it by pure thought.
About the same time, unemployment in the developed part of the world exceeds 50%
2049: The symbiotic computer: The separation between human and computer is getting more fuzzy as large parts of our brain are connected directly to the computer. We can no longer survive without it. 
2058: The sentient computer: As human and computer fully merge into one speices, the symbiots, the computer becomes self-aware, but on a level that we cannot comprehend. If the human brain survives it will have the same relationship to the computer as our reptile brain has to the celebral cortex. Whatever part of our bodies that are needed to sustain whatever part of our brains that will be left, survives. The rest dissapears.
2067: The end of the human race: The naturals, those humans who decides to stay un-augmented by computer power, is enslaved and later exterminated as their utility for the symbiots subseedes the cost of keeping them alive.

Alla som har jobbat i Scrum, och det har väl de flesta av oss vid det här laget, har säkert ställt sig frågan: Vilken uppgift ska jag ta mig an härnäst?

Den mer generella frågan, som den här bloggposten vill ge ett svar på, är: Hur ska uppgifterna i backloggen prioriteras mot varandra?

Det här brukar vara mer konst än vetenskap och man gör det lite på känn; vid behov, när det finns tid över, eller vid särskilda prioriteringsmöten. Aspekter som brukar vägas in är hur lång tid uppgiften tar, hur viktig den är och kanske även dess svårighetsgrad.

För att försöka lösa det här problemet en gång för alla har jag identifiera fem olika kriterier som jag anser bör vägas in i bedömingen. Vidare har jag rangordnat dessa kriterier mot varandra och slutligen har jag satt ihop dem till en formel som tar hänsyn till rangordningen och som ger en totalsumma att prioritera efter, utifrån hur många poäng man satt på varje dimension.

Alltså: Istället för att sätta upp fingret i luften och känna efter hur uppgifterna bör prioriteras, bedömmer man dem enskilt i fem olika dimensioner och räknar sedan fram en inbördes rangordning.

De fem kriterierna är som följer:

  1. Funtamentalitet
    - det första kriteriet har att göra med hur central för systemets ändamål en uppgift är. Jag anser detta vara det viktigaste kriteriet. Ett system bör byggas inifrån ut. Om inte kärnfunktionaliteten fungerar tillfredställande spelar det mindre roll hur bra kringfunktionerna är. Lägger man för stor vikt vid kringfunktioner så kanske man utvecklar fel system och borde istället utveckla ett system där kringfunktionerna är centrala. Alltså; börja med fundamental funktionalitet och gå sedan mot allt mer perifera funktioner när det mer basala finns på plats och fungerar som det ska

  2. Enkelhet
    - För att undvika att bygga på den tekniska skulden så bör man som nummer två värdera hur en uppgift påverkar systemets totala komplexitet. Om det finns uppgifter som minskar den totala komplexiteten eller åtminstone inte ökar den så bör man ta dem före uppgifter som ökar komplexiteten. Jobbar man i den riktningen så skapar man allt hållbarare fundament som kan bära upp en allt större funktionell överbyggnad. Annorlunda uttryckt: De uppgifter som idag adderar mycket komplexitet kan imorgon addera liten eller ingen komplexitet om man först har förstärkt och renodlat fundamenten/koncepten.

  3. Värde
    - Först på tredje plats har jag rankat det som vid första anblicken kan tyckas vara det viktigaste kriteriet, nämligen hur stort värde en funktion adderar till systemet. Anledningen är att ett IT-system är som ett isberg; den lilla synliga toppen motsvarar funktionalitet som ger ett direkt värde för kunden och allt annat är stödstrukturer som krävs för att bära upp de värdebärande funktionerna. Efter hand som ett system växer sig större och mer komplext minskar den värdebärande funktionalitetens andel av den totala massan. Om man bara satsar på att addera värde så riskerar överbyggnaden att bli större än vad fundamentet kan bära upp och hela systemet kollapsar under sin egen tyngd. Därför bör fundamentalitet och komplexitet värderas över värdeskapande.

  4. Svårighetsgrad
    - svårighetsgrad är i stort sett ekvivalent med risk: Risk att man inte hittar någon som är kompetent nog att utföra uppgiften, risk att oväntade problem uppstår under utvecklingen, risk att funktionen skapar nya buggar, och risk att man har missbedömt någon av de andra kriterierna. Därför bör svårighetsgraden vägas in när man prioriterar uppgifter. Börja med det som är enkelt. Ju längre arbetet framskrider desto mer erfarenhet samlar utvecklarna och ju starkare fundament finns att bygga mer komplicerade funktioner på. Bygger man rätt så tenderar svåra uppgifter som man skjuter på framtiden att te sig allt enklare med tiden och kommer därför prioriteras allt högre, allt annat lika.

  5. Tidsåtgång
    - Sist och faktiskt minst väsentlig är tidsåtgången. I de flesta IT-projekt beror en överhängande del av tidsåtgången på att man får betala för gamla synder, det vill säga handskas med teknisk skuld. Det kan röra sig om så mycket om en faktor tio eller en faktor hundra i tidsskillnad mellan att bygga en funktion på ett starkt och väl underhållet fundament och att bygga den på ett svagt och överkomplext fundament. Därför blir tidsåtgången ofta mer en funktion av hur väl underhållet systemet är än en funktion av hur stor uppgiften är. Icke desto mindre bör naturligtvis tidsåtgången beaktas eftersom effektiviteten i utvecklingen totalt sett är en funktion av värde delat med tid.

Man poängsätter alltså varje uppgift efter var och en av ovan beskrivna dimensioner. Sedan summeras poängen, och uppgifter med lägre poäng prioriteras före uppgifter med högre poäng.

För att väga in rangordningen mellan dimensionerna så har de olika skalor, där de viktigare dimensionerna har fler poäng att fördela än de mindre viktiga. Närmare bestämt har Fundamentalitet sju poäng att fördela, Enkelhet sex poäng och så vidare, ner till Tidsåtgång som har tre poäng. Varför en kortare poängskala ger en mindre total vikt i sammanräkningen behöver kanske motiveras. Man kan tänka så här: Differensen mellan minsta och största poäng är mindre ju kortare skalan är, alltså ger en kortare skala en mindre addering till totalsumman. 

Poängskalorna är som följer:

Fundamentalitet:

1: Fundamental
   - utan vilken systemet inte kan köras

2: Essentiell:
  - utan vilken systemet inte är ändamålsenligt

3: Kärnfunktion
 - utan vilka systemet inte kan anses fullt funktionellt

4: Basal funktion
 - utan vilka systemet inte kan anses komplett

5: Extra funktionalitet
 - som lägger till det som behövs i anslutning till det basala och gör systemet fullödigt

6: Nice to have / bra att ha

 - som ytterligare förbättrar systemet

7: Goldplating / Förgyllning
 - som adderar det lilla extra, t ex snyggare layout, grafiska effekter eller arbetsbesparande extra kommandon.

Enkelhet:

-2: Väsentlig förenkling

-1: Viss förenkling

0: Ingen förändring i komplexitet

1: Viss komplexitetsökning

2: Väsentlig komplexitetsökning

3: Mycket stor komplexitetsökning

Värdeskapande

0: Skapar inget direkt värde för användaren (refaktorering)

-1: Litet värde

-2: Medel värde

-3: Stort värde

-4: Mycket stort värde

Svårighetsgrad:

1: Trivialt

2: Enkelt

3: Medel

4: Svårt

Tidsåtgång:

1: Kort (minuter)

2: Medel (någon eller några timmar)

3: Lång (många timmar eller flera dagar)

Genom lite huvudräkning får man snabbt fram att totalsumman som man prioriterar efter kan variera mellan -3 och +17, alltså en total differens på 20; där Tidsåtgång adderar 2, Svårighetsgrad 3 och så vidare, upp till Fundamentalitet som adderar 6 steg.  

Slutligen:

  • En uppgift som är mycket svår ska inte utföras direkt, istället ska man titta på vilka förändringar i fundament och arkitektur som krävs för att förenkla uppgiften

  • En uppgift som tar lång tid (3 poäng) ska alltid delas upp i mindre uppgifter som värderas var för sig och prioriteras mot övriga uppgifter på nytt

  • Uppgifter kan ha beroenden som dikterar i vilken ordning de måste utföras. I sådana fall är det den högst rankade uppgiftens rangordning som bestämmer när andra uppgifter som den är beroende av ska utföras.

  • En uppgift som har ett positivt värde om man summerar enkelhet och värde (alltså skapar mer komplexitet än värde) bör tas bort från backloggen. En uppgift där enkelhet + värde = 0 bör noga övervägas vilket problem den löser och om problemet kan lösas på ett sätt som samtidigt ökar systemets samlade värde och potential (= enkelhet). Om inte bör en motivering till varför uppgiften är värd att utföras skrivas in på uppgiftskortet. 
Den ovan beskrivna metodiken / algoritmen är förmodligen lite för hardcore för de flesta Scrum/Kanban-projekt, men i stora projekt där prioritering mellan många uppgifter under tajta tidsramar blir ett stort huvudbry för projektledare och scrummästare kanske den kan tjäna som inspiration, eller rent av går att tillämpa rakt av.