Gebeurtenisbronnenpatroon
In plaats van alleen de huidige status van de gegevens in een domein op te slaan, gebruikt u een archief waaraan alleen gegevens kunnen worden toegevoegd, om de volledige reeks acties te registreren die op die gegevens wordt uitgevoerd. Het archief dient als het systeem van records en kan worden gebruikt voor het realiseren van de domeinobjecten. Hiermee kunnen taken in complexe domeinen worden vereenvoudigd door het synchroniseren van het gegevensmodel en het bedrijfsdomein te omzeilen, terwijl de prestaties, de schaalbaarheid en de reactietijd worden verbeterd. Het kan ook consistentie voor transactiegegevens bieden en volledige audittrails en -geschiedenis handhaven die compenserende maatregelen mogelijk maken.
Context en probleem
De meeste toepassingen werken met gegevens en gewoonlijk wordt de huidige status van de gegevens onderhouden door deze bij te werken terwijl ermee wordt gewerkt. In het klassieke CRUD-model (Create, Read, Update, Delete -maken, lezen, bijwerken, verwijderen) worden in een typisch gegevensproces gegevens vanuit het archief gelezen en enigszins gewijzigd, en wordt de huidige status van de gegevens bijgewerkt met de nieuwe waarden, vaak door gebruik te maken van transacties die de gegevens vergrendelen.
De benadering met CRUD kent enkele beperkingen:
CRUD-systemen voeren bijwerkbewerkingen rechtstreeks in een gegevensarchief uit. Hierdoor kunnen vanwege de vereiste overhead bij het verwerken de prestaties en het reactievermogen vertraagd en de schaalbaarheid beperkt worden.
In een samenwerkingsdomein met talloze gelijktijdig werkende gebruikers, is de kans op conflicten bij het bijwerken van gegevens groter, omdat de bijwerkbewerkingen op één gegevensitem worden uitgevoerd.
Hierbij gaat de geschiedenis verloren, tenzij er een aanvullend controlemechanisme is dat de details van elke bewerking in een afzonderlijk logboek vastlegt.
Oplossing
Het gebeurtenisbronnenpatroon definieert een benadering voor het afhandelen van bewerkingen op gegevens die op een reeks gebeurtenissen is gebaseerd. Elke gebeurtenis wordt vastgelegd in een archief waaraan alleen gegevens kunnen worden toegevoegd. Door de toepassingscode wordt een reeks gebeurtenissen verzonden waarmee elke actie die op de gegevens in het gebeurtenisarchief is uitgevoerd (en waar ze definitief worden bewaard), dwingend worden beschreven. Elke gebeurtenis stelt een reeks gegevenswijzigingen voor (zoals AddedItemToOrder).
De gebeurtenissen worden definitief in een gebeurtenisarchief opgeslagen. Dit fungeert als het recordsysteem (de gezaghebbende gegevensbron) voor de huidige status van de gegevens. Normaal gesproken worden deze gebeurtenissen gepubliceerd, zodat gebruikers op de hoogte kunnen worden gesteld en de gebeurtenissen zo nodig kunnen verwerken. Een gebruiker kan bijvoorbeeld taken initiëren die de bewerkingen in de gebeurtenissen toepassen op andere systemen, of een andere eraan gekoppelde actie uitvoeren die is vereist om de bewerking te voltooien. De toepassingscode waarmee de gebeurtenissen worden gegenereerd, wordt losgekoppeld van de systemen die zich op de gebeurtenissen abonneren.
De door het gebeurtenisarchief gepubliceerde gebeurtenissen worden gewoonlijk gebruikt voor het onderhouden van gerealiseerde weergaven van entiteiten als ze door acties in de toepassing worden gewijzigd, en voor integratie met externe systemen. Een systeem kan bijvoorbeeld een gerealiseerde weergave van alle klantorders onderhouden, die wordt gebruikt om delen van de gebruikersinterface te vullen. Naarmate er nieuwe orders worden toegevoegd, er artikelen aan de order worden toegevoegd of eruit verwijderd, en er verzendgegevens worden toegevoegd, kunnen de gebeurtenissen die deze wijzigingen beschrijven, worden verwerkt en gebruikt om de gerealiseerde weergave bij te werken.
Daarnaast kan de toepassing op elk moment de geschiedenis van de gebeurtenissen lezen en gebruiken om de huidige status van een entiteit te realiseren door alle gebeurtenissen die aan die entiteit zijn gerelateerd, af te spelen en te gebruiken. Dit kan op aanvraag gebeuren om een domeinobject te realiseren bij het verwerken van een aanvraag, of door middel van een geplande taak, zodat de status van de entiteit kan worden opgeslagen als een gerealiseerde weergave ter ondersteuning van de presentatielaag.
De afbeelding toont een overzicht van het patroon, waaronder enkele opties voor het gebruik van de gebeurtenisstroom, zoals het maken van een gerealiseerde weergave, het integreren van gebeurtenissen met externe toepassingen en systemen, en het opnieuw afspelen van gebeurtenissen om projecties te maken van de huidige status van bepaalde entiteiten.

Het gebeurtenisbronnenpatroon biedt de volgende voordelen:
Gebeurtenissen zijn onveranderlijk en kunnen worden opgeslagen met een bewerking waarbij alleen gegevens kunnen worden toegevoegd. De gebruikersinterface, de werkstroom of het proces dat een gebeurtenis heeft geïnitieerd, kan door blijven gaan, en taken die de gebeurtenissen verwerken kunnen op de achtergrond worden uitgevoerd. In combinatie met het feit dat er geen conflicten optreden tijdens het verwerken van transacties, kan dit de prestaties en de schaalbaarheid voor toepassingen aanzienlijk verbeteren, met name voor het presentatieniveau of de gebruikersinterface.
Gebeurtenissen zijn eenvoudige objecten waarmee wordt beschreven dat een bepaalde actie is opgetreden, in combinatie met eventuele gekoppelde gegevens die nodig zijn om de actie te beschrijven die door de gebeurtenis wordt vertegenwoordigd. Gebeurtenissen werken een gegevensarchief niet rechtstreeks bij. Ze worden op het juiste moment slechts geregistreerd voor verwerking. Hiermee kunnen de implementatie en het beheer worden vereenvoudigd.
Gebeurtenissen hebben doorgaan betekenis voor een domeinexpert, terwijl vanwege objectrelationele onverenigbaarheid complexe databasetabellen soms lastig te begrijpen zijn. Tabellen zijn kunstmatige constructies die de huidige status van het systeem vertegenwoordigen, niet de gebeurtenissen die hebben plaatsgevonden.
Met gebeurtenisbronnen kan worden voorkomen dat gelijktijdige updates conflicten veroorzaken, omdat de vereiste voor het rechtstreeks bijwerken van objecten in het gegevensarchief wordt vermeden. Het domeinmodel moet echter nog steeds zodanig worden ontworpen dat aanvragen die tot een inconsistente status kunnen leiden, worden voorkomen.
Dat gebeurtenissen worden opgeslagen in een archief waaraan alleen gegevens kunnen worden toegevoegd, biedt een audittrail die kan worden gebruikt voor het bewaken van acties die in een gegevensarchief zijn ondernomen, voor het regenereren van de huidige status als gerealiseerde weergaven of projecties door de gebeurtenissen op een willekeurig moment opnieuw af te spelen, en voor het testen van en fouten opsporen in het systeem. Daarnaast biedt de vereiste om compenserende gebeurtenissen te gebruiken om wijzigingen te annuleren een geschiedenis van teruggedraaide wijzigingen, wat niet het geval zou zijn als bij dit model alleen de huidige status zou worden opgeslagen. De lijst met gebeurtenissen kan ook worden gebruikt voor het analyseren van de prestaties van toepassingen en het detecteren van trends in gebruikersgedrag, of om andere nuttige bedrijfsinformatie te verkrijgen.
Het gebeurtenissenarchief activeert gebeurtenissen en taken voeren bewerkingen uit als reactie op die gebeurtenissen. Deze ontkoppeling van de taken van de gebeurtenissen biedt flexibiliteit en uitbreidbaarheid. De taken kennen het type en de datum van de gebeurtenis, maar niet de bewerking die de gebeurtenis heeft geactiveerd. Bovendien kan elke gebeurtenis door meerdere taken worden verwerkt. Hierdoor is eenvoudige integratie mogelijk met andere services en systemen die alleen letten op nieuwe gebeurtenissen die door het gebeurtenissenarchief worden geactiveerd. De gebeurtenissen met betrekking tot gebeurtenisbronnen zijn meestal van een erg laag niveau. Het kan daarom noodzakelijk zijn in plaats daarvan specifieke integratiegebeurtenissen te genereren.
Gebeurtenisbronnen worden gewoonlijk gecombineerd met het CQRS-patroon door gegevensbeheertaken uit te voeren als reactie op de gebeurtenissen, en door weergaven vanuit de opgeslagen gebeurtenissen te realiseren.
Problemen en overwegingen
Beschouw de volgende punten als u besluit hoe u dit patroon wilt implementeren:
Het systeem is uiteindelijk pas consistent als er gerealiseerde weergaven worden gemaakt of projecties van gegevens gegenereerd door gebeurtenissen opnieuw af te spelen. Er is enige vertraging tussen het toevoegen van gebeurtenissen aan het gebeurtenissenarchief als gevolg van het verwerken van een aanvraag, het publiceren van de gebeurtenissen en het verwerken ervan door gebruikers. Tijdens deze periode kunnen gebeurtenissen waarin verdere wijzigingen aan entiteiten worden beschreven, in het gebeurtenissenarchief zijn binnengekomen.
Notitie
Zie Data Consistency Primer (Inleiding tot gegevensconsistentie) voor informatie over uiteindelijke consistentie.
Het gebeurtenissenarchief is de permanente informatiebron. De gebeurtenisgegevens mogen dan ook nooit worden bijgewerkt. De enige manier om een entiteit bij te werken om een wijziging ongedaan te maken, is door een compenserende gebeurtenis aan het gebeurtenissenarchief toe te voegen. Als de indeling (niet de gegevens) van de persistente gebeurtenissen moeten worden gewijzigd (bijvoorbeeld tijdens een migratie), kan het lastig zijn bestaande gebeurtenissen in het archief te combineren met de nieuwe versie. Mogelijk dienen alle gebeurtenissen die wijzigingen aanbrengen, doorlopen te worden, zodat ze aan de nieuwe indeling voldoen. Of er dienen nieuwe gebeurtenissen te worden toegevoegd die de nieuwe indeling gebruiken. U kunt bijvoorbeeld een versiestempel gebruiken op elke versie van het gebeurtenissenschema, zodat zowel de oude als de nieuwe indeling behouden blijft.
Mogelijk kunnen zowel toepassingen met meerdere threads als meerdere exemplaren van toepassingen gebeurtenissen in het gebeurtenissenarchief opslaan. De consistentie van gebeurtenissen in het gebeurtenissenarchief is van vitaal belang, evenals de volgorde van gebeurtenissen die van invloed is op een bepaalde entiteit (de volgorde waarin wijzigingen aan een entiteit optreden, beïnvloedt de huidige status ervan). Door een tijdstempel aan elke gebeurtenis toe te voegen kunnen problemen worden voorkomen. Een andere veelgebruikte methode bestaat uit het toevoegen van aantekeningen aan elke gebeurtenis die het gevolg zijn van een aanvraag met een incrementele id. Als twee acties tegelijkertijd gebeurtenissen voor dezelfde entiteit willen toevoegen, kan het gebeurtenissenarchief een gebeurtenis weigeren die overeenkomt met een bestaande entiteit-id en gebeurtenis-id.
Er bestaan geen standaardmethoden of al aanwezige mechanismen (zoals SQL-query's) om gebeurtenissen te lezen om informatie te verkrijgen. De enige gegevens die kunnen worden geëxtraheerd zijn een stroom gebeurtenissen die een gebeurtenis-id als criteria gebruiken. De gebeurtenis-id is doorgaans toegewezen aan afzonderlijke entiteiten. De huidige status van een entiteit kan alleen worden bepaald door alle gebeurtenissen die er betrekking op hebben, opnieuw af te spelen tegen de oorspronkelijke status van die entiteit.
De lengte van elke gebeurtenisstroom is van invloed op het beheren en bijwerken van het systeem. Als de stromen groot zijn, kunt u op bepaalde tijdstippen momentopnamen maken, bijvoorbeeld van een bepaald aantal gebeurtenissen. De huidige status van de entiteit kan uit de momentopname worden verkregen en door alle gebeurtenissen die na dat tijdstip zijn opgetreden, opnieuw af te spelen. Zie Primary-Subordinate Snapshot Replicationvoor meer informatie over het maken van momentopnamen van gegevens.
Hoewel met gebeurtenisbronnen de kans op conflicterende updates van gegevens wordt geminimaliseerd, moet de toepassing nog steeds in staat zijn inconsistenties af te kunnen handelen die het gevolg zijn van uiteindelijke inconsistentie en het gebrek aan transacties. Een gebeurtenis die bijvoorbeeld een afname van de voorraad van een bepaald artikel aangeeft, kan in het gegevensarchief terechtkomen terwijl er een order voor dat artikel wordt geplaatst. Dit leidt ertoe dat de twee bewerkingen met elkaar in overeenstemming moeten worden gebracht door ofwel de klant te adviseren of door een nabestelling te maken.
Gebeurtenispublicatie kan ten minste één keer zijn en daarom moeten gebruikers van de gebeurtenissen idempotent zijn. Zij moeten de update die in een gebeurtenis staat beschreven, niet opnieuw toepassen als de gebeurtenis vaker wordt verwerkt. Als bijvoorbeeld meerdere exemplaren van een consument de eigenschap van een entiteit onderhouden en aggregeren, zoals het totale aantal geplaatste orders, hoeft er slechts één de aggregatie te verhogen wanneer er een gebeurtenis plaatsvindt die een bestelling heeft geplaatst. Hoewel dit geen belangrijk kenmerk van gebeurtenisbronnen is, is dit het gebruikelijke implementatiebesluit.
Wanneer dit patroon gebruiken
Gebruik dit patroon in de volgende scenario's:
Als u de bedoeling, het doel of de reden in de gegevens wilt vastleggen. Wijzigingen in een klantentiteit kunnen bijvoorbeeld worden vastgelegd als een reeks specifieke gebeurtenistypen, zoals Verplaatst naar huis, Gesloten account of Naar.
Als het cruciaal is het optreden van conflicterende updates aan gegevens te minimaliseren of volledige te voorkomen.
Als u optredende gebeurtenissen wilt registreren en ze opnieuw wilt kunnen afspelen om de status van het systeem te herstellen, wijzigingen terugdraaien of een geschiedenis en auditlogboek bijhouden. Als bijvoorbeeld voor een taak meerdere stappen nodig zijn, moet u wellicht acties uitvoeren om updates terug te draaien en vervolgens enkele stappen opnieuw af te spelen zodat de gegevens in een consistente status kunnen worden teruggebracht.
Als het gebruik van gebeurtenissen een natuurlijke functie van de werking van de toepassing is en hiervoor weinig aanvullende ontwikkeling of implementatie is vereist.
Als u het proces voor het invoeren en bijwerken van gegevens wilt ontkoppelen van de taak die is vereist om deze acties toe te passen. Dit kan om de prestaties van de gebruikersinterface te verbeteren of om gebeurtenissen te distribueren naar andere listeners die actie ondernemen als de gebeurtenissen plaatsvinden. Bijvoorbeeld: het integreren van een salarissysteem met een website waarin onkosten kunnen worden ingediend zodat gebeurtenissen die door het gebeurtenissenarchief worden geactiveerd als reactie op updates aan gegevens op de website, worden gebruikt door zowel de website als het salarissysteem.
Als u bij veranderende vereisten flexibel genoeg wilt zijn om de indeling te kunnen wijzigen van gerealiseerde modellen en entiteitsgegevens of (indien gebruikt in combinatie met CQRS) als u een leesmodel of de weergaven waarop de gegevens zichtbaar zijn, moet aanpassen.
Bij gebruik in combinatie met CQRS en uiteindelijke consistentie acceptabel is terwijl een leesmodel wordt bijgewerkt, of als de invloed van de prestaties bij het reactiveren van entiteiten en gegevens van een gebeurtenissenstroom acceptabel is.
Dit patroon is wellicht niet geschikt in de volgende situaties:
Kleine of eenvoudige domeinen, systemen met weinig of geen bedrijfslogica of niet-domeinsystemen die in het algemeen goed werken met traditionele CRUD-gegevensbeheermechanismen.
Systemen waarbij consistentie en updates aan de weergaven van de gegevens in realtime zijn vereist.
Systemen waarbij audittrails, geschiedenis, de mogelijkheid tot terugdraaien en afspeelacties niet zijn vereist.
Systemen waarbij er een zeer kleine kans is op conflicterende updates voor de onderliggende gegevens. Bijvoorbeeld systemen die hoofdzakelijk gegevens toevoegen in plaats van ze bij te werken.
Voorbeeld
Een beheersysteem voor conferenties moet het aantal voltooide reserveringen voor een conferentie bijhouden, zodat kan worden gecontroleerd of er nog plaatsen beschikbaar zijn als een potentiële deelnemer een reservering wil maken. Het systeem kan het totale aantal reserveringen voor een conferentie op ten minste twee manieren opslaan:
Het systeem kan informatie over het totale aantal reserveringen opslaan als een aparte entiteit in een database waarin de reserveringsgegevens worden bewaard. Als er reserveringen worden gedaan of geannuleerd, kan dit aantal worden verhoogd respectievelijk verlaagd. Deze benadering is in theorie eenvoudig, maar kan aanleiding geven tot schaalbaarheidsproblemen als een groot aantal deelnemers in korte tijd plaatsen wil reserveren. Bijvoorbeeld op de laatste dag voordat de reserveringsperiode afloopt.
Het systeem kan informatie over reserveringen en annuleringen opslaan als gebeurtenissen die in een gebeurtenissenarchief worden bewaard. Vervolgens kan het aantal beschikbare plaatsen worden berekend door de gebeurtenissen opnieuw af te spelen. Deze benadering kan meer schaalbaar zijn vanwege de onveranderlijkheid van de gebeurtenissen. Het systeem hoeft alleen maar gegevens te lezen in het gebeurtenissenarchief of gegevens toe te voegen aan het gebeurtenissenarchief. Gebeurtenisinformatie over reserveringen en annuleringen wordt nooit gewijzigd.
In het volgende diagram wordt getoond hoe het subsysteem voor het reserveren van plaatsen van het conferentiebeheersysteem kan worden geïmplementeerd door middel van gebeurtenisbronnen.

De volgorde van de acties voor het reserveren van twee plaatsen is als volgt:
De gebruikersinterface geeft de opdracht plaatsen te reserveren voor twee deelnemers. De opdracht wordt afgehandeld door een aparte opdrachthandler. Dit is een stukje logica dat wordt ontkoppeld van de gebruikersinterface en verantwoordelijk is voor het afhandelen van aanvragen die als opdrachten zijn gepost.
Er wordt een aggregatie met informatie over alle reserveringen voor de conferentie gebouwd door query's uit te voeren in de gebeurtenissen waarin de reserveringen en annuleringen staan beschreven. Dit aggregaat wordt
SeatAvailabilitygenoemd en bevindt zich in een domeinmodel dat methoden beschikbaar maakt voor het uitvoeren van query's in gegevens en het wijzigen van gegevens in het aggregaat.Overweeg de volgende optimaliseringen: momentopnamen (zodat u geen query's hoeft uit te voeren en de volledige lijst met gebeurtenissen hoeft af te spelen om de huidige status van het aggregaat te verkrijgen) en een kopie van het aggregaat in de cache in-memory onderhouden.
De opdrachthandler roept een methode aan die door het domeinmodel beschikbaar wordt gemaakt om reserveringen te maken.
Het
SeatAvailability-aggregaat registreert een gebeurtenis met het aantal gereserveerde plaatsen. De volgende keer dat het aggregaat gebeurtenissen toepast, worden alle reserveringen gebruikt om te berekenen hoeveel plaatsen er nog resteren.Het systeem voegt de nieuwe gebeurtenis toe aan de lijst met gebeurtenissen in het gebeurtenissenarchief.
Als een gebruiker een plaats annuleert, volgt het systeem een soortgelijk proces. Hierbij wordt een opdracht gegeven waarmee een annuleringsgebeurtenis wordt gegenereerd die aan het gebeurtenissenarchief wordt toegevoegd.
Het gebruik van een gebeurtenissenarchief biedt niet alleen meer ruimte voor schaalbaarheid, maar tevens een volledige geschiedenis, of audittrail, van de reserveringen en annuleringen voor een conferentie. De gebeurtenissen in het gebeurtenissenarchief zijn vormen de juiste record. Aggregaten hoeven niet definitief op een andere manier te worden opgeslagen, omdat het systeem de gebeurtenissen eenvoudig opnieuw kan afspelen en de status naar elk tijdstip kan terugzetten.
In Introducing Event Sourcing (Inleiding tot gebeurtenisbronnen) vindt u meer informatie over dit voorbeeld.
Volgende stappen
Blog van Martin Fowler:
Verwante informatie
De volgende patronen en richtlijnen zijn mogelijk ook relevant bij de implementatie van dit patroon:
Command and Query Responsibility Segregation (CQRS) patroon. Het leesarchief (de permanente bron van informatie voor een CQRS-implementatie) wordt vaak gebaseerd op een implementatie van het gebeurtenisbronnenpatroon. Het beschrijft hoe de bewerkingen die gegevens in een toepassing lezen, door middel van afzonderlijke interfaces moeten worden gescheiden van de bewerkingen die gegevens bijwerken.
Materialized View-patroon. Het gegevensarchief dat wordt gebruikt in een systeem dat is gebaseerd op gebeurtenisbronnen, is gewoonlijk niet geschikt voor het efficiënt uitvoeren van query's. Daarom worden vaak weergaven van de gegevens gegenereerd die vooraf zijn ingevuld. Dit kan op regelmatige basis zijn of als de gegevens worden gewijzigd. Toont hoe dit kan worden uitgevoerd.
Patroon Compenserende transactie. De bestaande gegevens in een gebeurtenisbronnenarchief worden niet bijgewerkt. In plaats daarvan worden nieuwe vermeldingen toegevoegd die de status van entiteiten naar nieuwe waarden overdragen. Voor het terugdraaien van een wijziging, wordt gebruikgemaakt van compenserende vermeldingen, omdat het niet mogelijk is de vorige wijziging eenvoudig terug te draaien. Beschrijft hoe het werk dat door een eerdere bewerking is uitgevoerd, ongedaan kan worden gemaakt.
Gegevensconsistentie-primer. Bij het gebruik van gebeurtenisbronnen met een afzonderlijk leesarchief of gerealiseerde weergaven, zijn de leesgegevens niet onmiddellijk consistent, maar slechts uiteindelijk consistent. Vat de problemen samen rond het handhaven van consistentie bij gedistribueerde gegevens.
Richtlijnen voor gegevenspartitionering. Gegevens worden vaak gepartitioneerd als u gebeurtenisbronnen gebruikt om de schaalbaarheid te verbeteren, conflicten te verminderen en de prestaties te optimaliseren. Beschrijft hoe gegevens in discrete partities moeten worden verdeeld en de problemen die kunnen optreden.