De terminologie van ES-CQRS

De terminologie van ES-CQRS

Eén van de uitdagingen bij het in gebruik nemen van Event Sourcing (ES) en Command Query Responsibility Segregation (CQRS) is dat er nog geen gevestigde terminologie is. De uitleg die je wel kunt vinden, is regelmatig te vaag om er met iedereen over te praten.

Afgelopen woensdag waren (bijna) alle Procurios-ontwikkelaars bij een ES-CQRS workshop, gegeven door FourScouts. Bij FourScouts hebben ze veel ervaring met het gebruik van ES-CQRS, in veel verschillende projecten en contexten. Ze hebben daarbij een redelijk evenwichtig beeld gekregen van termen en de betekenis die anderen in de praktijk aan die termen geven.

Wat is Event Sourcing?

De gangbare manier om gegevens op te slaan is het Active Record patroon. We bewaren de huidige staat van een object en werken die bij als het object verandert.

Bij Active Record is de huidige staat (het record) de bron van waarheid. Wat er in het verleden gebeurd is (transacties, logs, historie), is een afgeleide.

Bij Event Sourcing is dat omgekeerd. De verzameling van gebeurtenissen vormt de bron van waarheid en de huidige staat is een afgeleide daarvan.

No silver bullet

Door het inherente inzicht in het verleden is het relatief eenvoudig om logging, audit trails, undo/redo of een tijdlijn te bouwen.

Gecombineerd met DDD zal de tijdlijn bestaan uit betekenisvolle gebeurtenissen die de intentie van de gebruiker aangeven.

Gecombineerd met CQRS zal het makkelijk zijn om het Read Model te voeden.

Dat klinkt geweldig, maar Event Sourcing heeft zo z'n eigen valkuilen, nadelen en consequenties.

Eén van die valkuilen is het gebrek aan een gevestigde terminologie. Aangezien gedeelde taal onder collega's belangrijk is, is het goed om het eens uit te schrijven. De praktijkervaring van FourScouts, aangevuld met onze eigen ervaring, kan daarbij een ideaal startpunt zijn.

Terminologie

CQRS

Command Query Responsibility Segregation. Het scheiden van de lees- en schrijfkant van een applicatie.

ES

Event Sourcing. De opgeslagen gebeurtenissen (Events) vormen de bron van waarheid, waaruit de huidige staat is af te leiden. In tegenstelling tot het Active Record patroon waarbij de huidige staat de bron van waarheid is.

ES en CQRS staan los van elkaar. Het één is niet afhankelijk van het ander. De reden dat ze vaak samen genoemd worden, is omdat ze conceptueel met elkaar in synergie zijn en om die reden in de praktijk goed bij elkaar passen.

Command

De wens van een gebruiker om de huidige staat te veranderen wordt gerepresenteerd door een Command. De naamgeving werkt het best in gebiedende wijs (ConfirmOrder, AcceptAppointment).

Command Handler

Een Command Handler valideert of een Command uitgevoerd kan worden (op dit moment, op de huidige staat), en past het Command toe op een Aggregate. Voor elk Command is er precies één Command Handler.

Hoewel er geen consensus is over de technische implementatie van Command Handlers, kom je in de praktijk het meest tegen dat een Aggregate zelf de Command Handler heeft.

Event

Een Event representeert iets dat gebeurd is in het domein. Het is het resultaat van een Command. Een Command kan 0 of meerdere Events tot gevolg hebben. De naamgeving werkt het best in verleden tijd (OrderConfirmed, AppointmentAccepted).

Aggregate

Een Aggregate is een Entity (of een groep van Entities) die (intern) altijd in een consistente staat is. Het vertegenwoordigt en begrenst één aspect van het domein waar de Aggregate bijhoort.

De Aggregate State is de huidige staat van de Aggregate, zoals die volgt uit de geschiedenis van gebeurtenissen.

De Aggregate valideert in de Command Handler een binnenkomend Command, wijzigt zijn eigen staat en genereert Events.

Aggregate en Entity worden in de praktijk door elkaar gebruikt. Het woord Aggregate heeft een andere lading, in die zin dat de context ervan anders is (ES-CQRS, DDD).

Aggregate Root

Externe referenties naar een Aggregate zijn beperkt tot een daarvoor aangewezen Entity binnen de Aggregate. Deze Entity noem je de Aggregate Root. Alleen via de Aggregate Root kan gecommuniceerd worden. In de praktijk kom je regelmatig tegen dat een Aggregate uit één Entity bestaat, en daarmee de Aggregate en Aggregate Root samenvallen.

Een concreet voorbeeld. Een BlogPost Aggregate zou kunnen bestaan uit twee Entities: BlogPost en Comments. Om het geheel in een consistente staat te houden, zou het toevoegen van een comment gedaan moeten worden via de BlogPost. In dit geval is deze Entity de aangewezen Aggregate Root.

Command Bus

De buitenwereld praat tegen de Command Bus. Die weet voor een Command welke Aggregate de Command Handler heeft.

Dat is nuttig vanwege "separation of concerns" - je wilt niet dat elke buitenwereld weet wat hij met een Command moet.

De Command bus kan synchroon of asynchroon werken (fire and forget). In de praktijk vaak synchroon, zodat je de validatie door de Command Handler kunt afwachten.

In dat geval is het eigenlijk niet echt een Bus:
- Er is een één op één wiring tussen Command en Command Handler
- Er komt informatie terug van de Command Handler

Soms wordt het een Command Gateway genoemd. Het is in feite de API op je domein voor de app / infrastructure.

Event Bus

De tegenhanger van de Command Bus is de Event Bus.

De Aggregate genereert 0 of meerdere Events en die worden vastgelegd in de Event Store. Dat laatste is belangrijk, omdat de waarheid éérst vastgelegd moet zijn, voordat doorgegeven wordt aan de buitenwereld wat er gebeurd is. De Event Bus heeft voor een Event 0 of meerdere luisteraars, de Event Listeners, die iets met het Event kunnen doen.

Event Store

De opslag voor Events. Welke technologie gebruikt wordt, is voor de betekenis van deze term niet relevant (maar voor je domein soms wel).

Belangrijk is dat events opgeslagen worden in de volgorde waarin ze zijn gebeurd. Na opslag ligt de volgorde vast.

Naast de Events wordt metadata opgeslagen zoals datum, tijd, ingelogde gebruiker, systeeminformatie.

Event Listener

Ook Event Handler genoemd. Een Event Listener reageert op een Event, zoals Project bijwerken, e-mail sturen, webservice-call doen, berichtje naar de browser sturen.

Projection

Een Projection is een weergave van de huidige staat van één of meerdere aggregates, en wordt opgebouwd door alle relevante events af te spelen en te verwerken. Een Projection kan opgeslagen zijn in MySQL, Mongo, Redis, xml-bestand - of wat je maar wilt.

Deze representatie kan allerlei namen hebben; denk aan persistent Read Model, View of State.

Read Model

Projections die specifiek bedoeld zijn voor het uitlezen van gegevens uit je domein. In de praktijk zie je dat Read Models gemaakt worden voor specifieke toepassingen. Zo kan je de data optimaliseren voor de queries die erop gedaan worden en bijvoorbeeld joins in een query voorkomen.

Snapshot

Een Snapshot legt de Aggregate State vast op een bepaald moment. Het vervangt de Events die ervoor zijn gekomen.

De Events worden gebruikt om de Aggregate State op de bouwen. Als dat er veel zijn (duizenden, miljoenen) kan het zinvol zijn om Snapshots bij te houden. Samen met de Events die erna komen, levert het de huidige Aggregate State op.

Een Snapshot kan worden geimplementeerd als een "mega-Event" waarbij de huidige staat als Event wordt opgeslagen. Een alternatief is als meta-data naast de Event Store.

Een voordeel van een Snapshot opslaan als Event is dat je Projections ook naar de SnapshotEvents kunnen luisteren.

Een nadeel van een Snapshot opslaan als Event is dat er een implementatiedetail (namelijk de interne staat van de Aggregate) de Event Store in lekt.

Eventual Consistency

Het gegeven dat iets niet direct Consistent is (of hoeft te zijn). Denk bijvoorbeeld aan de situatie dat een Projection asynchroon aan het Event wordt bijgewerkt. De Projection is dan Eventually Consistent: het systeem garandeert dat de Projection uiteindelijk bijgewerkt zal zijn, maar niet onmiddellijk.

Saga

Ook Long Lived Process of Process Manager genoemd.

Er zijn zeer waarschijnlijk processen in het domein die meerdere Aggregates raken, en/of langer lopen. Denk bijvoorbeeld aan het aanmeldingsproces van een gebruiker.

In sommige domeinen is er pas een gebruiker na validatie. De validatie op zichzelf is een domeinconcept, en als de validatie is afgerond wordt er pas een gebruiker aangemaakt. Dat proces wordt gerepresenteerd door de Saga. Waar iemand zich bevindt in het aanmeldingsproces is State die door de Saga wordt bijgehouden.

Ten slotte

Werken met ES-CQRS is niet meer cutting edge te noemen. Daar bestaat het te lang voor (zeker het gedachtengoed erachter). Desondanks is het nog lang niet gevestigd en zijn er veel meer bedrijven die conventioneel werken. Je blijft soms het gevoel houden dat je aan het pionieren bent, en je wat je doet niet kunt valideren tegen 'hoe het hoort'.

Een gedeelde en vastgelegde terminologie helpt bij het voeren van gesprekken en bij het vormen van gedachten. Aangezien er nog geen officieel vastgelegde terminologie is, is ook dit een grijs gebied en soms onderdeel en oorzaak van discussie. Het kan dan prettig zijn om op de praktijk terug te vallen, en wat dat betreft hoop ik dat bovenstaand verhaal je verder helpt.

Heb jij andere ervaringen? Goede voorbeelden, of aanvullingen? Laat het weten, dan verwerken we dat!

Leave a comment...

Leave a comment

Italic en bold

*Dit is italic*, en _dit ook_.
**Dit is bold**, en __dit ook__.

Links

Dit is een link naar [Procurios](http://www.procurios.nl).

Lijsten

Een lijst met bullets kan worden gemaakt met:
- Min-tekens,
+ Plus-tekens,
* Of een asterisk.

Een genummerde lijst kan worden gemaakt met:
1. Lijst-item nummer 1.
2. Lijst-item nummer 2.

Quote

Onderstaande tekst vormt een quote:
> Dit is de eerste regel.
> Dit is de tweede regel.

Code

Er kan een blok met code worden geplaatst. Door voor de tekst vier spaties te plaatsen, ontstaat een code-block.