Event sourcing in een legacy systeem – Deel 1 – Context is koning

event-sourcing background

In deze blog reeks wil ik het hebben over het CQRS/ES (Command Query Separation met Event Sourcing) architectuur patroon en specifiek hoe je deze architectuur techniek ook in een bestaand legacy systeem kan gebruiken.

De essentie van CQRS is simpel: waar er voorheen slechts 1 service was zijn er nu 2: 1 service geoptimaliseerd voor schrijven en de andere geoptimaliseerd voor lezen.
Dit idee komt voort uit het inzicht dat in het overgrote deel van applicaties er vele malen meer data wordt gelezen dan dat er data wordt weggeschreven. CQRS laat je toe om voor deze 2 perspective anders te schalen. 1 concreet voorbeeld is ORMs en meer specifiek de lazy loading feature, die is typisch zeer nuttig bij het schrijven van gegevens maar typisch niet bij het lezen, als je bijvoorbeeld op een efficiënte en performante manier een rapport wil genereren kan je vaak beter een gewone SQL query schrijven en specifiek de data ophalen die je nodig hebt (zonder ORM & dus zonder lazy loading).

ES bouwt verder op het idee van CQRS door in het schrijf gedeelte van de applicatie het domein model voor te stellen als een stream van events. In plaats van een “statisch” relationeel model dat bij elke update wordt aangepast (typisch met behulp van een ORM) bouw je het domein model bij elke aanpassing opnieuw op door dynamisch alle events van een specifiek aggregate af te spelen (in de juiste volgorde!). Op die manier reconstrueer je de huidige state van die aggregate in-memory waarna je dan nieuwe aanpassingen kan doen die dan opnieuw tot nieuwe events leiden.

In deze blog reeks wil ik vooral focussen op waarom en hoe we dit architectuur patroon hebben toegepast in een legacy applicatie (een grote monoliet die al jaren succesvol in productie draaide).

Merk ook op dat ik het hier specifiek heb over ES in de context van DDD, ES in deze context is vooral gefocust op het modelleren van een business domein met events. Dit staat dus volledig los van 1 of andere specifieke technologie. Veel mensen denken tegenwoordig aan Kafka als ze Event Sourcing horen en in de context van IoT devices die events uit sturen of het verwerken van data door middel van event streams is Kafka ook zeker een relevant product.

Voor de vorm van Even Sourcing waar ik het echter hier over heb is Kafka niet geschikt, we hebben namelijk een vorm van optimistic locking nodig om er zeker van te zijn dat alleen events die door de huidige state van het domein model gevalideerd zijn worden weggeschreven (op dit moment is dit niet mogelijk in Kafka en na jaren wachten op support hiervoor lijkt de kans klein dat dit weldra zal worden toegevoegd).

Context is koning

Eén van de belangrijkste principes van DDD is dat “context is king”, het is daarom erg belangrijk dat je elke techniek evalueert tegenover de specifiek context waar je in werkt. Er zijn geen “silver bullets” dus je moet altijd goed evalueren, met voortschrijdend inzicht, wat de meest geschikte oplossing is voor jouw specifieke context (dit is erg moeilijk!).
Zonder te veel in detail te gaan zal ik daarom hier proberen de omstandigheden te scheppen waarin we ons bij JIDOKA bevonden bij een bepaald project.

Hieronder volgt een korte samenvatting van een aantal observaties die we maakten over de bestaande software (AS-IS):

  • Het bestaande systeem was +- 4 jaar oud en was relatief groot qua hoeveelheid code. (relatief tegen over de andere custom software van die klant)
  • De architectuur was een klassieke gelaagde monoliet, die uit een viel in meerdere modules met een klein aantal verschillende web applicaties en achterliggend 1 grote databank
  • De software was voor het grootste deel geschreven in Java (Spring stack + Hibernate) met Oracle Database als RDBMS maar er was ook een aanzienlijk deel geschreven in PL/SQL
  • Er waren een hele boel batch jobs die ’s nachts/tijdens het weekend liepen om bepaalde KPI rapporten te generen & bepaalde validaties uit te voeren
  • We kregen 1 jaar tijd om een groot deel van het systeem te herschrijven, voornamelijk voor 2 redenen: 
    1. Eén grote business epic/feature die uit een viel in nieuwe functionaliteit die doorheen het systeem nodig was.
    2. De kwaliteit van bepaalde stukken van het systeem was erg laag (o.a. rapporten die nooit juist waren, sommige schermen die erg traag waren, usability issues, etc.)
  • De performantie van het systeem was over het algemeen goed behalve voor een aantal KPI rapporten die ontzettend traag waren (het laden van 1 rapport kon gemakkelijk 5 minuten duren!)
  • De schaalbaarheid van het systeem was ok, desondanks dat deze erg gelimiteerd was vanwege de monolithische architectuur

We begonnen deze uitdaging met een strategische DDD oefening namelijk een context map maken van de bestaande situatie. Dit nam ongeveer 2 à 3 dagen in beslag en was achteraf bekeken een erg waardevolle oefening, door op dit hoge niveau naar de business context te kijken en hoe de bestaande software hierin paste konden we onze gevechten kiezen en vooral focussen op de delen die behoorde tot het core domein.

Bij dit concrete project kwamen we tot de conclusie dat er ongeveer een 10tal subdomeinen waren in het business domein waarvan we 3 subdomeinen als “core domains” beschouwde. Voor alle 3 deze core subdomains was er software geschreven in het monolithische legacy systeem (geen gescheiden bounded contexts dus). Met de opdracht die we gekregen hadden wilde we ons vooral focussen op 1 specifiek core subdomain, in een ideale wereld zouden we hiervoor een nieuwe applicatie bouwen met zijn eigen bounded context en domein model maar in de echte wereld moesten we opzoek naar een geschikte technische oplossing binnen de constraints en deadlines van het project.

The right tool for the right job

Het werd al snel duidelijk dat het belangrijkste core domain opnieuw implementeren in zijn eigen bounded context niet haalbaar was binnen de constraints van het project. Wat wel tot de mogelijkheden behoorde was het hermodeleren van de belangrijkste aggregate binnen het systeem. Eén van de belangrijkste technische beperkingen hier was dat de bestaande database tabel (met ongeveer 100 database kolommen!) door vele verschillende stukken van de software werd gelezen (zowel via Hibernate in Java code als rechtstreeks in PL/SQL code). Dus de opties om deze aggregate opnieuw te modelleren leken initieel beperkt.

Na wat brainstormen en Hammock Driven Development organiseerde we uiteindelijk een spike waarin we wilden proberen of ES een geschikte oplossing was voor dit deel van de applicatie, de redenen waarom we dit wilde proberen waren de volgende:

  • De aggregate in kwestie werd veel vaker gelezen dan geüpdatete → CQRS leek hier dus wel goed te passen
  • De aggregate had al een “poor man’s versie” van event sourcing, de Hibernate entity was namelijk (gedeeltelijk) geversioneerd met Hibernate Envers → Auditing was dus in de bestaande software al erg belangrijk, door ES te gebruiken krijg je vanzelf een audit log van alles wat er met een bepaalde aggregate gebeurd 
  • De aggregate in kwestie was de hoofdreden dat de KPI rapporten van deze applicatie zo traag waren (zelfs nadat ze al voor een groot stuk gegenereerd werden met batch jobs en gevisualiseerd werden met materialized views) → bij een ES aanpak zou elk rapport zijn eigen projectie (leesmodel) kunnen zijn, een soort materialized view on steroïden
  • De aggregate zelf bevatte een hele boel informatie die gerelateerd was aan tijd, er waren bijvoorbeeld verschillende gebruikers die informatie op de aggregate moesten aanpassen, voor elke soort aanpassing werd een apart datum veld bijgehouden in de databank om achteraf te weten wanneer welke data wanneer veranderd was → in de bestaande software was “tijd” dus een belangrijke component, iets wat heel natuurlijk is bij een ES aanpak
  • De verschillende gebruikers die verantwoordelijk waren informatie op de aggregate aan te passen deden dit onafhankelijk van elkaar, ze hadden ook nooit onmiddellijk informatie van elkaar nodig (omdat ze o.a. vaak in verschillende tijdzones en met verschillende business uren werkte) → 1 van de nadelen van ES is dat het systeem voor een stuk eventually consistent moet worden, in de bestaande applicatie was dit al op een natuurlijke manier, binnen de business context, het geval en dus geen echte constraint.

Al deze factoren leidde er toe dat ES toepassen op deze specifieke aggregate een goeie match leek te zijn voor het probleem voor handen.

Uiteindelijke twijfelde of we dit systeem misschien ook voor andere aggregates te gebruiken maar uiteindelijk beslisten we dit toch niet te doen, de complexiteit van de oplossing zou voor veel aggregates hoger liggen dan de voordelen.

In de volgende blogpost duiken we dieper in de technische details en hoe we CQRS/ES hebben toegepast in dit legacy systeem.

Van statisch naar dynamisch, een tools page verhaal

Lees meer

Yoo-bik-wi-tee

Lees meer

Hoe kunnen softwareteams twee keer zoveel waarde in half zoveel tijd leveren?

Lees meer