*Nano-service
*Distributed monolith
Vad är bra kod? Vilka regler bör man följa? Jag började att läsa boken Clean code och började skriva om mina tankar runt den boken. Men det är historia och nu skriver jag vidare...
En agil mjukvaruutveckling brukar beskrivas som att man låter programvaran växa genom evolution i stället för genom en förutbestämd plan. Man hävdar alltså (korrekt) att det är svårt att i förväg förstå vilka konsekvenser beslut om en applikation får, förrän man faktiskt upplever hur det fungerar i "verkligheten", alltså när det är implementerat och klart.
Så, om du är agil så ska du göra fel, ändra, se att det fortfarande inte är bra och ändra igen. När du är klar med detta så ska du välkomna förändrade krav. Men i de projekt jag har jobbat i har man ansett att en utforskande ansats är för dyr, eftersom det är svårt att förändra när saker väl blivit skrivna. När kraven förändras väljer man ta långa omvägar för att det är så svårt att ändra i det man redan har. Med andra ord väljer man bort ett agilt förhållningssätt och lägger stor möda på att försöka att göra rätt på första försöket.
Jag ska nedan försöka beskriva några mönster och dess möjlighet till att göra det lättare att arbeta agilt. Dessa mönster kommer inte gratis på något sätt. Man kan se det lite som att med dessa mönster hoppas man på en komplexitetsökning som ser ut som O(log n) där den traditionella modellen något elakt skulle efterlikna O(n*n) (n i kvadrat alltså). Dock så börjar den agila modellen med en mycket större insats i kunskap och stödjande tjänster för att inte leda till katastrofens rand. Vet du inte vad du gör är det läge att låta bli och inte härma de som faktiskt vet vad de gör och varför.
Microservices
Med microservices väljer man att dela upp sin applikation i många små fristående delar i stället för en. Varje microservice blir simpel men man blir inte av med komplexiteten, utan den flyttar ut i nätverket där man hoppas att den ska gå och hantera på ett bättre sätt. Microservices stödjer det agila arbetssättet genom att du kan byta ut en microservices relativt enkelt utan att påverka resten av systemet och du är fri att ta beslut om hur din microservices ska fungera internt utan hänsyn till hur de andra är implementerade, kanske byta till ett mer lämpat programmeringsspråk, uppgradera bibliotek etc etc.
Prestanda brukar vara den brukar beskrivas som drivkraften för microservices, men jag skulle hävda att möjligheten till att olika utvecklingsteam kan arbeta utan att påverka varandra är mycket starkare i normalfallet (Amazon och Netflix är inte normalfall)
Eventsourcing
Med eventsourcing sparar du ditt data utifrån vilka förändringar som har gjorts i systemet (verben) i stället för att lagra entiteter (substantiven). Man kan jämföra med en läkare som har sparat din journal (med alla undersökningar, prover och anteckningar) och använder den som grund för att ställa diagnos och väljer att inte ordinera jordnötter eftersom det står att du är allergisk i journalen till motsats till en akutläkare som ser att blodet sprutar och får utgå från det hen kan se just nu.
Med eventsourcing har du inte en modell utan snarare en modell per händelse och en modell per slutsats eller rapport du önskar skapa från dina händelser, även om du naturligtvis kan ha en övergripande modell någonstans i bakgrunden. Flera modeller gör det svårare att hitta hur ett enskilt attribut kom till världen men som å andra sidan gör att du kan anpassa dina modeller efter olika behov i stället för att anpassa behovet efter modellen. Attribut som tex rubrikfärg har jag sett många gånger krypa in i tex en person-entitet eller telefonnummer-entitet där man kanske hade varit mer betjänt av att ha en modell över presentations-stöd och en annan över domän-information. Så i stället för att ha en modell där en förändring påverkar all annan funktionalitet väljer man att kunna förändra en funktions modell separat från de andra. Notera att detta kan innebära en hel del dubbellagring av data och synkronisering av detta data måste ske på ett kontrollerat sätt.
Immutable
Att vara immutable innebär att man aldrig modifierar sitt data, man lägger bara till ny information till systemet. Det kanske låter som lite slöseri med hårddisk men det ger en del intressanta möjligheter, främst att du får möjlighet att titta på hur ditt data såg ut vid en tidigare tidpunkt. Som extra bonus blir cachning enkelt, eftersom den information som finns där aldrig förändras (det kommer ny information naturligtvis, men de gamla värdena är orörda).
Man kan dra immutable-begreppet längre än data och ta in begreppet immutable-everywhere. I princip innebär det att du aldrig ändrar i integrationer, du skapar en ny och den gamla får leva vidare tills det är säkert att ta bort den. Du ändrar aldrig i dina installationer på dina servrar utan gör en ny och låter den gamla leva vidare tills det är säkert att ta bort den gamla. Det ger bland annat möjlighet att skapa en ny integration till en ny klient som innehåller alla de senaste funktionerna, samtidigt som övriga klienter kan fortsätta att använda den gamla tjänsten tills de är redo att uppgradera. Drar man den tanken till sin spets får varje klient sin egen tjänst och kan där med förändras oberoende av andras behov.
Även vid serverdrift, särskilt i mer eller mindre virtuella sammanhang kan man välja att skapa en ny "image" som kan driftsättas parallellt med den gamla installationen och testas om den fungerar i produktion på en delmängd av trafiken innan man "pekar om" alla användare till den nya servern.
Automatisering
Människan är ganska dålig på att utföra repetitiva uppgifter och detta leder till ett kontrollbehov, eftersom vi kort och gott inte är att lita på. Detta leder till ett behov av kontrollstrukturer som i sig själva driver behovet av fler människor som ska utföra alla kontroller och i sin tur kontrolleras och ingen tillför värde för användaren av systemet (åtminstone inte direkt).
Vägen ut ur detta är automatisering. Efter att ha kontrollerat att automaterna(?) fungerar (tex gör rätt sak vid feltillstånd) kan de släppas lösa. Eftersom du kan ha ett större förtroende för att saker och ting fungerar kommer du också våga att förändra och förbättra i dina system. Detta leder till att ditt system blir större efter som fler uppgifter behöver programmeras och underhållas.
Sammanfattning
Att vara agil är så mycket mer än att ha en iterativ process. Det handlar också om att inte bara vara en döv, stum och blind idiot för vad kraven på systemet säger idag utan även för vad de kan komma att säga i morgon och förutsätta att saker kommer att förändras. Att placerat sig i en position där man kan välkomna förändringar även sent i projektet.
De mönster jag skrivit om ovan kommer inte gratis utan är dyra, både i tid och kunskap. De löser vissa problem men för med sig många andra som du också måste förstå innan du börjar, oavsett projektstorlek. Men när du börjar få problem med att du inte längre kan anpassa ditt system snabbt och smidigt kommer du önska att du tittat på ovanstående mycket tidigare.
Ibland får man uppenbarelser, en känsla av att man förstått något och nu vet hur en aspekt av världen fungerar och varför det är som det är. Allt är självklart och nu är det dags att agera och inte svamla något om kanske och eventuellt.
Professionellt som programmerare är det ganska långt mellan varven som jag upplevt detta men nu de sista 1-2 åren har det hänt ganska ofta. Begrepp som
* händelsebaserat
* immutable
* functional
* reactive
* single responsibility
* continious delivery
* micro services
har allt gått som en blixtar genom mitt huvud.
Det är inte så att jag levt i en bunker isolerad från omvärlden. Det är inte nytt, men en förståelse för vad de faktiskt innebär och vad var de hör hemma har kommit till mig (hoppas jag i alla fall).
Den senaste blixten i allt detta är insikten att ingen av dessa är fantastisk i sig själv utan att de tillsammans bildar en helhet. En ganska radikalt annorlunda helhet än den vi är vana med men ändå en helhet som tar sin utgångspunkt i reaktion. Detta kanske motställt till den traditionella modellen som åtminstone i praktiken fokuserar på planering och förutsägelse.
Jag har haft förmånen att få jobba med folk med erfarenhet ett antal gånger, jag menar närmare 50 års erfarenhet av programmering. Det som jag ofta hör, när dessa erfarna personer beskriver hur de gjorde förr, är det vi försöker att åstadkomma nu. Vi har bara varit ute på en utflykt, en normaliserad och transaktionstät utflykt, men just bara en utflykt.
Min tes är att någon gång i samband med att vi fick tillgång till RDBMS så slutade vi hantera två viktiga aspekter av vår programvara, tid och felhantering.
När man förr tog en fil (eller två) som indata till sitt program körde sitt program och producerade en ny fil var det data man hade i praktiken immutable. Om man upptäckte ett fel i programvaran kunde man kör det uppdaterade programmet utifrån de gamla filerna. Om man sparade sina filer hade man ett arkiv över alla förändringar man gjort på sitt data. Tiden var kanske inte lika mycket "up your face" som den är i eventsourcing, men den fanns där. Att modellera med tid på ett normaliserat sätt är ingen barnlek någonstans, för beroende på vilket delsystem som körs har de olika och ofta motstridiga krav som är väldigt svåra att slå ihop.
Vilket leder oss till nästa del av min tes. Förr, när man hade kört ett jobb och kontrollerade om det gått som det skulle, då fick man hantera vad som skulle hända. Om något gått fel tex fick någon besluta om att köra jobbet en gång till, tillkalla utvecklaren eller kanske till och med börja om från en tidigare tidpunkt. Det är lite som att lägga ett meddelande på en kö, man får välja hur man vill hantera om något gått fel, tex göra omskick, låta meddelandet dö, eller notifiera tjänsten som skickade meddelandet. I vilket fall är detta något som måste hanteras.
I dagens system tappar vi bort tiden eftersom vi glatt skriver över och raderar värden i vår databas utan information om vem, när eller varför. Om något går fel gör vi en rollback och tappar all information om vad som gjordes. Smidigt många gånger, men våra system är inte så flexibla på att möta nya krav och vår kompetens i hur man hanterar olika problemsituationer är låg.
Med tekniker som eventsourcing och microservices behöver vi återigen lära oss att leva utan den alltid närvarande transaktionen och vi måste lära oss att hantera problemsituationer på fler sätt än att rulla tillbaka en transaktion.
Jag fick en gång lära mig att fördomar kommer ifrån att vi människor inte är tillräckligt smarta för att kunna hantera allt i världen individuellt, utan istället måste generalisera och begränsa den information vi hanterar. Så det är lönlöst att bekämpa fördomar, de är ett nödvändigt redskap för att vi ska fungera. Men det vettiga personer gör, regelbundet och ofta, är att ifrågasätta sina fördomar och sprida dem med stor försiktighet. Så i stället för att säga att "så är det" kanske man använder uttryck mer i stil med "i min erfarenhet" eller liknande.
Med detta intro tänkte jag utforska den som jag upplever den största fördomen inom it, nämligen att dubbelt är dåligt.
Sedan jag började med programmering har jag fått lära mig kod ska organiseras på ett sådant sätt att man inte behöver skriva samma logik två gånger. Det är egentligen inget att ifrågasätta om det gör korrekt, men i korrekt ligger haken. För hur identifierar man vad som är samma logik och vad som inte är det?
En tanke man kan ha i bakhuvudet när man ska eliminera duplicerad kod är att en dålig abstraktion är dyrare än duplicerad kod. Med det menas att om du har tagit ett beslut om att slå ihop funktioner som ligger utspridda i systemet till en enda, då bör du inte bara vara säker på att den kod du refaktorerar representerar samma logik idag, utan även i morgon. För hur ska du hantera att en funktion som är beroende på din funktion i morgon har önskemål på en liten variation i din funktion? Duplicera din funktion och modifiera kopian? Lägga till en if-sats?
Problemet omfattas av single responsibility pattern. Din kod ska bara ha en anledning att ändras. Så om ekonomiavdelningen vill se vissa attribut på en produkt som inte får visas till kund på webben (täckningsbidrag tex) kan det vara bättre att ha två olika produktklasser, anpassade för sina respektive behov och inte riskera att ändringar i en ände av koden påverkar något annat.
Mitt andra exempel på "dubbelt är dåligt" är runt lagring. Vi anstränger oss bland annat hårt för att normalisera våra databaser så att inget data ska finnas på mer än ett ställe och det är först när vi tvingas av prestandaskäl som vi bryter "dubbelt är dåligt" och då oftast genom att använda en cache.
Men precis som med duplicerad kod är det en god idé att ifrågasätta om den enda datamodellen är den rätta. Tänk en ansökan som ska gå igenom en process, där olika attribut i datamodellen är relevanta beroende på var i processen du är. Ansökan har kanske ansökta uppgifter, bekräftade uppgifter, beslutade uppgifter och ett antal uppgifter som är relevanta för alla tre stadierna. Alla attributen tillhör en ansökan och refererar till samma identitet så det är samma ansökansobjekt. Kanske det då kan vara bättre att ha tre separata objekt, som representerar sitt respektive tillstånd, trots att ett antal uppgifter då blir dubbellagrade, att bara de uppgifter du behöver är tillgängliga och de uppgifter som är brus är borttagna ur scopet.
När man börjar gruppera sin modell efter vad man ska göra kan man få en modell där samma objekt dyker upp flera gånger men med olika attribut beroende på vad man ska göra. Den stora vinsten med detta tänk är att man designar bort många missförstånd genom att eliminera möjligheten att använda attribut som inte är relevanta. Å andra sidan måste man snurra igenom och sammanfoga de olika varianterna till en rapport för att kunna se tex hur många objekt man har och vilket tillstånd dessa objekt är i då data inte är lagrat på ett enda sätt längre. Som jag skrivit någon gång tidigare, komplexitet försvinner inte utan du kan bara välja hur du vill hantera den. Men jag vågar påstå att anpassa objekt till situation är en möjlighet vi slagit dövörat för då vi varit rädda för att dubbellagra information.
Om man ska utveckla ett "modernt datasystem", nu i slutet av 2015 kan man inte undvika termen immutable.
De enda anledningarna till att bygga något med mutable state är prestanda givet några specifika krav och ohejdad vana. Men faktum är att vi inte haft datorer förrän kanske de 10 (?) senaste åren där vi faktiskt kunnat hantera immutable state på ett sådant sätt att vi kunnat dra nytta av det till vardags. Vi har tex inte haft minne nog för att inte välja att återvinna det vid första tänkbara tillfälle och hårdvara har varit dyrt. Så, trots att det finns mycket forskning i ämnet har det bara varit akademiska övningar. I praktiken var vi tvungna att använda alla trick och möjliga optimeringar vi kunde och därav har lösningar och processer varit mutable.
Men idag med servrar där terrabytes av ramminne är och petabytes av långtidslagring är möjlig har vi en helt annan situation och dåtidens krav på optimeringar är idag är ofta kontraproduktiva. För som ni vet, optimeringens första lag: optimera inte förräns du faktiskt vet att det behövs, gäller fortfarande.
Det finns ett antal dåliga aspekter med låta saker ändra på sig i systemet och det spelar i sak inte roll om vi pratar om integrationer mellan system, installationer av system eller ett värde på ett fält i en klass. De ger alla problem med synkronisering och om du ändrar en del av systemet (oberoende var) riskerar du att ställa till det för en annan del av systemet. Och synkronisering är svårt, väldigt svårt och vi behöver inte ens ha ett flertrådat system för att få problem med mutable state.
Så, finns det någon bra lösning på detta som inte introducerar en massa ny komplexitet? Svaret är naturligtvis nej, men vi kan välja att hantera en annan komplexitet som vi tror att vi har större möjligheter att bemästra.
En sådan lösning skulle kunna vara att enbart lägga till information i systemet och aldrig inom ramen "den dagliga verksamheten" radera (inte ens uppdatera) existerande information. Ändra inte värden på objekt, skapa nya. Ändra inte i listor av värden, skapa nya listor, ändra inte i existerande tjänster, skapa nya tjänster, ändra inte i integrationer, skapa nya.
Med tekniker som immutable infrastructure så finns det ingen gräns för att "inte ändra, skapa nytt", utan det handlar mer om den mognad som din organisation har för vad som går att realisera och vilka steg som är möjliga för att förbättra situationen. Om det tar tex din driftsavdelning 4 veckor att sätta upp en " ny" server som kan köras parallellt med den gamla tills den gamla blivit utfasad, ja då har ni en resa framför er. Andra saker som att ta bort alla setters från dina klasser och bara initiera genom konstruktörer och factories kanske är ett enklare steg.
Vad blir vinsten med att bara lägga till ny information i ditt system? Du blir av med synkroniseringsproblemet. Bara en sådan enkel sak som att en yttre loop inte behöver vara orolig för att en inre loop ändrar på förutsättningarna för den yttre är värd mycket. I andra sammanhang tillåter oförändringsbara objekt en närmast oproblematisk cachning, för du behöver aldrig invalidera förändrade objekt för att något har ändrat på det, bara lägga till nya. Historik blir enkelt när du inte har skrivit över den gamla informationen.
Ja, så summa summarum, har du och stället du arbetar på inte börjat att ta till er ordet immutable på allvar i hur ni arbetar är det hög tid att göra det nu. Det finns mycket att läsa och ämnena är många, allt från funktionella språk till eventsourcing till docker containers. Men allt landar i att mutable state borde förpassas till historien, tillsammans med mänskliga computers, hålkort och goto.
För någon vecka sedan återupptäckte jag den här bloggen som jag började skriva i samband med att jag läste Robert "Uncle Bob" Martins bok "Clean code".
Ja, det blev några blogginlägg och jag drog igång Jönköping Developer Dojo i processen. Det var roligt att läsa igen och väckte en hel del minnen.
Det har runnit en hel del vatten under bron sedan dess så jag tänkte försöka att dela med mig av en del erfarenheter jag har tagit med mig sedan dess, om sådant som inte gått så bra och om sådant som har gått bra och förmodligen en hel del av sådant jag skulle vilja göra nästa gång jag får bestämma över hur saker ska göras.
Ord som immutable, simple, complect (nej, inte complex), essensial complexity, monolit m m kommer säkerligen att dyka upp lite här och var.
Ja, nu får vi se hur detta går. En ambition har blivit nedskriven i en blogg. Återstår att se vad det blir för resultat.