Jag sitter och pillar med en presentation som jag eventuellt ska hålla någon gång i framtiden. Kanske skulle våga mig på att spela in den här gången.
Till denna presentation behöver jag ett case på hur en applikation kan utvecklas om man tar Open/Closed-principle lite mer på allvar.
Steg 1
En entitet, Person. Fälten id och namn.
En tjänst, PersonRepository. Fem metoder, create, read, update, delete och list100AfterId. De fyra första slänger en händelse, PersonUpdated. Sparar data i en relationsdatabas, tabellen Person.
Vi produktionsätter.
Steg 2
Vi upptäcker en brist i PersonEntiteten, det borde vara separata fält för förnamn och efternamn. För att inte förstöra för alla klienter till PersonRepository väljer vi att skapa en ny entitet, Person2 och ett nytt repository Person2Repository som sparar i tabellen Person2. Person2Repository använder list100AfterId från PersonRepository för att synka upp existerande data. För att fortsätta vara i sync lyssnar p2r även på PersonUpdated-händelsen.
Vi produktionsätter, allt fortsätter att fungera som tidigare. Vi kan bekräfta att den nya tjänsten fungerar som den ska och successivt skriva om klienter från den gamla tjänsten till den nya.
Steg 3
Ett behov av att kunna söka efter personer har identifierats. Då vi inte vill förändra i Person2Repository då det också skulle kräva ändringar hos tjänstens klienter väljer vi att skapa en PersonSök-tjänst.
PersonSök använder list100AfterId för att bygga upp ett index (Lucene, Elasticsearch, egen tabell i databasen eller något annat). Vi väljer att inte smälta ihop PersonRepository och PersonSok genom att integrera dessa i databasen. PersonSok lämnar alltid en lista av PersonId som svar på en sökning.
Vi produktionsätter. Det gamla rullar på, det nya kan verifieras och klienter kan snart börja använda.
Steg 4
Vi ska en tid senare implementera en tjänst som kan hitta personer som är nära dig. Vi kallar den för NäraDig. NäraDig använder sig av PersonSök7 som låter dig söka på fälten senastKändaPosition och returnera PersonId sorterat på tidFörSenastKändaPosition. Efter sökning hämtas Person-objekt från Person14Repostitory. Eftersom sök-tjänsten kan returnera många svar är vi oroliga för om Person14Repository orkar med den förväntade lasten. Därför implementerar vi olika anrops-scheman, ett anrop i taget, x synkrona anrop i taget och alla anrop på en gång. Vi implementerar en feature-toggle för att kunna ändra schema utan att behöva stoppa några tjänster.
Steg 5
Det visar sig att Person14Repository inte har tillräckligt goda prestanda för att NäraDig ska kunna användas och att kundnyttan är för dålig om man begränsar antalet personer som man hämtar information om. Man väljer att öka prestanda i PersonRepository.
I p15r byter man databas, egenskapen man söker är att man kan öka prestanda genom att lägga till fler noder av databasen. Man skriver dessutom om så att alla anrop till metoden read hanteras asynkront så att en instans av p15r kan hantera flera hundra tusen samtidiga anrop till read-metoden.
Steg 6
Person har nu efter ett antal "förändringar" blivit en trång punkt. Man väljer att lägga till nya fält för ofta, vilket leder till att övriga delar av systemet måste följa med (och synkronisering över flera versioner över lång tid är pita). Inom Person-entiten identifieras ett flertal objekt, namn, adresser, telefonnummer, epost, positioner, arbetsplatser etc, etc. Så nu har vi en mängd med entiteter med lika många repositories.
Steg 7
Det visar sig att förändringarna i steg 6 blir dyra att implementera då varje klient behöver skriva om all funktionalitet som berör Person till att hantera en mängd nya objekt. Så vi inför en ny tjänst, PersonAggregat. PersonAggregat samlar ihop all information om en Person som vi bröt isär i steg 6. Klienterna kan nu välja att bygga sina egna Person-objekt eller använda det färdigbyggda aggregatet.
Steg 8
Verksamheten vill kunna ta ut rapporter från det data som systemet innehåller, men har noterat att vi har minst tre datakällor, den vanliga databasen, PersonDatabasen som egentligen är 10 databaser och sökindexet, vilket ställer till det för det valda rapportverktyget. Vi tar fram en datamodell och använder list100AfterId-metoderna för att fylla en rapporteringsdatabas. Uppsidan blir att man kan köra så mycket rapporter man önskar utan att störa produktionssystemen.
Det jag tycker är intressant i dessa steg är att de aldrig kräver att klienterna måste vara klara att använda det nya. Det problemet blir en administrativ fråga där man får väga problemet med att synkronisera datakällor mot att kunna gå frammåt.
Att vi helt undviker att utföra joins i databasen och i stället väljer att ha en aggregat-tjänst som gör joinen mycket senare och som tillåter underliggande struktur att förändras är också värt att notera.
Detta är bara ett hypotetiskt case. Många frågor måste lösas. Tex om vi har flera versioner av en tabell, vilken är sanningen och hur upprätthåller man den? Eller hur ska händelser propageras på ett rimligt sätt?