Event-driven Architecture: CQRS aanpak

 
19 februari 2010

Geschreven door: Jilles van Oossanen
In samenwerking met: Luc Kleeven en Anatoly Zimakov

Naar aanleiding van het artikel van André Boonzaaijer “Het nieuwe paradigma?” is ons gevraagd om onderzoek te doen naar het steeds populairder wordende fenomeen, de zogeheten “Event-driven Architecture”.

De Event-driven Architecture (EDA) beschrijft een situatie waarin alle componenten communiceren via events. Een voordeel hiervan zou kunnen zijn dat deze stateless componenten op hun beurt elk event in een aparte thread behandelen en daarmee dus volledig asynchroon worden. Een voordeel van deze ontkoppeling kan zijn dat een systeem zeer schaalbaar wordt. De vraag die wij ons stellen komt voort uit de voorlopige conclusie van Andre Boonzaaijer: “…deze vorm van systeemontwerp en -bouw zeer goed kunnen werken, maar slechts voor een beperkte set aan toepassingen.” Want om welke toepassingen gaat het dan? En waarom is het beperkt tot die set?

Ons onderzoek is begonnen met het lezen van artikelen en forumtopics. Ondanks het feit dat de EDA al een in 2003 voor het eerst beschreven is, zijn er helaas nog geen boeken over geschreven. Dit zorgde ervoor dat de beschikbare informatie vaak vaag was en weinig concrete ideeën of voorbeelden bevatte. Het was dus lastig om aan de hand van deze informatie tot een ontwerp van een applicatie met een EDA te komen.

Om een goede vergelijking te kunnen maken tussen een OO implementatie en een EDA implementatie, hebben we allereerst een OO implementatie van de applicatie gerealiseerd. Deze applicatie diende als leidraad, voor de functionaliteit, tijdens de ontwerp- en realisatiefases van de EDA versie.  Daarnaast kon met behulp van die applicatie goed in de gaten gehouden worden welke aanpassingen gedaan moeten worden om van een OO naar een EDA implementatie te gaan.

Nadat de OO implementatie was gerealiseerd, stond er een applicatie waarin alle functionaliteit was vastgelegd zoals deze in de EDA implementatie terug moest gaan komen. Nu was het dus zaak om een aantal dingen te bepalen voor het EDA ontwerp:

  • Welke events heb je?
  • Welke componenten/objecten heb je?
    • Welke verantwoordelijkheden hebben deze componenten/objecten?
  • Hoe houd je de status van je applicatie bij?
  • Hoe om te gaan met eventuele vertragingen tussen aanvraag van een gebruiker en de reactie van het systeem?
    • En hoe geef je dan bijv. foutmeldingen weer?

Tijdens het onderzoek waren we al gestruikeld over de derde vraag: “Waar houd je de status bij?” Het is namelijk erg onduidelijk hoe hier op geantwoord moet worden in het geval van een CRUD applicatie. Deze gaat namelijk uit van een Request/Response. En juist dat principe wordt met behulp van de EDA om zeep geholpen. Je hoeft, volgens de EDA, namelijk niet te verwachten dat het systeem waar je wat aan vraagt, ook direct antwoord gaat geven. Voor de EDA op zich waren hier weinig ideeën over te vinden. Er is echter wel een aanpak uitgewerkt onder de noemer Command Query Responsibility Segregation(CQRS, zoals André Boonzaaijer ook benoemd) waarmee mogelijk een oplossing wordt geboden.

CQRS beschrijft een situatie waarin commando’s en queries, beide vanuit de GUI, apart worden behandeld. Een query zal, zoals we dat ook zagen in onze OO implementatie, direct data ophalen uit een database. Een command zal echter via een bus in het domain eindigen. Hier wordt het commando verwerkt en wordt er een event afgevuurd, waarin beschreven staat wat er gebeurd is. Hier komt de event-driven aard van de aanpak naar boven. Dit event kan namelijk door elk component, aangesloten op de bus, worden opgevangen. Zo ook door de eventhandler van de eerder genoemde database, welke het op zijn specifieke manier zal afhandelen.

Deze aanpak bied al gauw een voordeel, er zijn namelijk voorbeelden van. Een daarvan is de Lottery applicatie van Erik Rozendaal. Met behulp van een eigen framework heeft hij een event-driven applicatie ontwikkeld, maar wel volgens de CQRS aanpak. Om meer gevoel te krijgen voor deze aanpak en de EDA zelf, hebben we deze applicatie omgebouwd om zo onze eigen functionaliteit te implementeren.

Tijdens dit ‘trial en error’ proces, hebben we een hoop geleerd over de CQRS aanpak. Toen alle gewenste functionaliteit aanwezig was, hebben we nog een keer goed gekeken naar de nieuwe applicatie en hebben we er onze lessen uit getrokken. Aan de hand van de geleerde lessen hebben we vervolgens een aantal regels opgesteld:

  • Alle logica behoort in het domein te zitten (vasthoudend aan DDD principes).
  • Het domein kan gezien worden als kleine subdomeinen, die op zichzelf staan.
  • Onderlinge communicatie tussen componenten gaat alleen via messages.
  • We onderscheiden drie types messages: commands, queries en events.
  • Commands zijn externe invloeden op het systeem, bijv. gui
  • Queries zijn externe verzoeken om informatie, vanuit bijv. de gui.
  • Events zijn interne berichten over gebeurtenissen, bijv. om de reporting op de hoogte te houden.
  • Message handlers doen enkel aan routing en mogen dus geen side-effects hebben, een message handler is altijd voor één specifiek message en naar één specifiek component.

Om de regels te visualiseren, maken we gebruik van het volgende model:

CQRS

Deze regels moeten als leidraad gelden bij het ontwikkelen van een CRUD applicatie welke de CQRS aanpak zo goed mogelijk volgt.

Aangezien de CQRS aanpak je in staat stelt om eenvoudig bij je data te komen, lijkt een groot probleem van de EDA opgelost te zijn. Dit geldt echter alleen voor de GUI. Het domain beschikt niet over deze functionaliteit. Hoe moet dat dan wanneer er gecheckt moet worden of een uniek item niet voor een tweede keer wordt toegevoegd? Moet je dan de event store terugspoelen om de status te achterhalen? En kost dit niet heel veel performance, voor een relatief kleine handeling? Naast deze vragen kan ook terecht worden opgemerkt dat de CQRS aanpak niet volledig EDA is wanneer je kijkt naar de GUI. Dus hoe zou een applicatie er uit zien welke een volledige EDA heeft?

Zoals André Boonzaaijer in zijn artikel al benoemde, is de EDA interessant, maar slechts voor een beperkte set aan toepassingen. Onze mening is dat een CRUD applicatie niet behoort tot deze set. Zoals we beschreven hebben is een dergelijke applicatie wel te realiseren met de CQRS aanpak. Het probleem is echter dat hier niet alles event-driven is (vandaar de naam). Wanneer je een volledig event-driven CRUD applicatie wil maken, blijven er een aantal vragen onbeantwoord.

Tot nog toe hebben wij deze vragen niet kunnen voorzien van een bevredigend antwoord. We gaan echter wel de applicatie weer opnieuw opbouwen, maar nu aan de hand van onze nieuwe regels. Hopelijk komen we dan tot nieuwe antwoorden en oplossingen, welke we dan ook zeker met jullie zullen delen. Uiteraard nodigen wij iedereen uit om mee te denken over deze vragen. Commentaar en/of opmerkingen zijn uiteraard ook van harte welkom.


Werken met ?
Kijk dan bij onze mogelijkheden voor starters en/of ervaren IT'ers.


Categorieën: Architectuur


Reacties (13)

  • cbaars schreef:

    Hallo,

    Ik meng me graag in deze discussie want ik heb redelijk wat ervaring mat EDA maar dan op systeem/enterprise niveau. Anatoly slaat de spijker op zijn kop. EDA moet je alleen toepassen als het nodig is omdat het gevaren kent zoals inconsistente data en iets dat ik zelf altijd de eventlawine noem. Als er een groot aantal listeners zijn voor een event is het risico dat ze allemaal tegelijk reageren en je een ongedefinieerde toestand in een systeem krijgt. Je kunt dit vergelijken met de problemen die optreden bij een plotselinge grote koersverandering van een aandeel op de beurs waarop zeer veel geautomatiseerde handelssystemen tegelijk reageren. En dan zijn die systemen in het algemeen nog onafhankelijk van elkaar.

    Zoals Anatoly al aangeeft is een EDA in essentie asynchroon. In Enterprise Service Bussen wordt wordt EDA toegepast om veel verschillende systemen direct en tegelijkertijd op de hoogte te brengen van een externe gebeurtenis. Dat is heel hip want dat past mooi bij de neiging van bedrijven om de klant centraal te stellen en derhalve klantgebeurtenissen zoals een verhuizing of ontslag direct te verwerken.

    Bij de Belastingdienst gebruiken we EDA als basis voor het toeslagensysteem waarbij direct de implicaties van een situatie wijziging van een toeslagrechthebbende verandert.

    Een andere toepassing van EDA waar ik nu bij de Belastingdienst An werk is het synchroniseren van Operational Datastores. Dit zijn kopie databases die voor de beschikbaarheid van gedeelde gegevens zorgdragen. We doen daar een soort cachesynchronisatie op systeemniveau om de kopieën van gegevensverzamelingen op de hoogte te brengen van de toestand van bepaalde gegevens. De kopie houdt bij of bepaalde gegevens dirty zijn en kan dan op een gewenst moment de actuele gegeven synchronisren. Dit doet een geclusterde database als Oracle RAC vanzelf ook maar dan op een meer atomaire schaal.

    Dit mechanisme vindt je ook terug in Master Data Management systemen die ook gedistribueerde gegevens synchroniseren. Beperking daarvan is dat die alleen werken voor relatief kleine gegevensverzamelingen zoals gedeelde referentietabellen.

    Uit deze voorbeelden blijkt al dat gegevens of State synchronisatie een essentieel probleem is voor EDA. Daarom pas je het ook in het algemeen niet toe voor CRUD en of transactionele systemen. In een eDA is het essentieel dat de publisher en listenercompleet ontkoppeld zijn. De publisher weet dus ook niet of er wel een listeners bestaat die ietsdoet met een event.

    Sterker nog, een broadcast EDA, dat is een variant waarbij iedere patij alle berichten krijgt en zelf de berichten selecteert die voor hem relevant zijn, is de meest loosely coupled architectuur die we kennen en die mogelijk is. Dat is mooi want dat betekent dat we aan het eind zijn gekomen van onze collectieve zoektochtnaar loosely coupled interfaces. Probleem is wel dat een broadcast EDA erg veel eist van het netwerk en dat, vanwege de losse koppeling transactionaliteituitgesloten is.

    Wanneer je echt stoer bent, bestuur je een EDA met rules. Dat heet choreography in de SOA wereld. Moet je echt alleen doen als het nodig is omdat de testbaaheid en de traceerbaarheid veelslechter is dan een tijdens design time vastgelegde flow. Toch ontkom je er soms niet aan om EDA en Rule toe te passen. Dat doe je in het geval van een niet deterministische besturing. Dat is het geval dat je tijdens design time niet de optimale executie volgorde in runtime kan bepalen. Een voorbeeld is een systeem waar ik bij de politie aan heb gewerkt en dat beslissers bij een grote ramp ondersteunt. Stel je wilt op een scherm het rampgebied tonen maar pas wanneer minstens tachtig procent van de hulpverleners de aankomst op hun bestemming hebben gemeld. Anders zou je per ongeluk kunnen denken dat er te weinig hulpverleners zijn, maar ze hadden zich alleen nog niet gemeld. Dit realeer je met een regel die in het conditiedeel steeds evalueert hoeveel hulpverleners zich hebben gemeld en in het conclusiedeel toon je het scherm. Bij een ramp zal het conditiedeel steeds waarderder worden tot de tachtig procent is bereikt en het scherm getoond wordt. Het moment waarop dit in runtime gebeurt weet je niet tijdens designtime en ook niet op welke manier deze actie uitpakt in relatie tot andere event.

    Zo dat waren mijn two cents voor nu. Ik wil er graag eens met een paar mensen over praten want EDA is als post SOA paradigma erg hip en ik hou mijn hart vast als ik de bedrijven zie die ermee aan de slag gaan zonder goed te kijken naar de toepasbaarheid en de risico’s

    Cor

    Oeps, ik zie dat ik op mij iPad niet meer terug kan scrollen om mijn typefouten te herstellen. Excuses daarvoor.

    Geplaatst op 13 augustus 2010 om 12:28 Permalink

    • Rob Vens schreef:

      Events moeten inderdaad veel meer gechoreagrafeerd worden (excusez le mot) dan de simplistische orchestratie die we meestal zien in SOA. Interessant dat je meldt dat dat heel goed kan met rules. Ik ben bij dergelijke oplossingen al snel benieuwd naar de wijze waarop je dan het probleem van rules management oplost. Dus dat je 1500 rules hebt of zo. Die wil je liever niet op een bult gooien. Je loopt het risico al snel dat je state (data), en gedrag (in dit geval de rules, en die rangschik ik in de categorie van pre- en postcondities van gedrag, maar dat even terzijde) in verschillende containers hebt draaien, waarvoor totaal verschillende management tooling nodig is, en die ook nogal verschillen in semantiek.
      Voor deze problematiek ben ik nog geen oplossingen tegengekomen, daar ben ik wel heel erg benieuwd naar.

      Geplaatst op 25 augustus 2010 om 13:24 Permalink

    • Hallo Cor,

      Binnen Sogyo ben ik al een tijd bezig met onderzoek naar Event Driven Architectures en het gebruik van Command-Query Responsibiity Segregation (CQRS) patroon voor de applicatie interface. Dat is een momenteel populair patroon en volgens mij een geoperationaliseerde en specifieke vorm van een EDA binnen de grenzen van wat normaal gesproken als 1 applicatie beschouwd wordt.

      Ik zie de voordelen van EDA zeker en denk dat er behoorlijk wat ruimte is om het te gebruiken in een range aan toepassingen. Tegelijk zie ik ook wel de risico’s die je noemt. Waar de grenzen liggen en hoe bepaalde risixo’s zijn tegen te gaan ben ik dan ook zoveel mogelijk aan het verkennen.

      Het lijkt me erg interessant om daarover van gedachten te wisselen met iemand met zoveel kennis en ervaring. Daarom ben ik erg blij dat we elkaar tegenwoordig tegen kunnen komen op de werkvloer. Als je wilt, dan stel ik voor om de discussie een keer offline voort te zetten.

      Met vriendelijke groet,
      Rick van der Arend

      Geplaatst op 15 augustus 2010 om 22:04 Permalink

      • Cor Baars schreef:

        Goec plan! Ik ben de komende weken in ieder geval elke donderdag in de Bilt. Misschien kunnen we dan een keer afspreken.

        Groet, Cor

        Geplaatst op 25 augustus 2010 om 12:11 Permalink

        • Ik ben er dan normaal gesproken op dinsdag en vrijdag. Deze en komende week lijkt het daar ook wel weer op uit te draaien. Ik zal eens kijken of er nog andere mogelijkheden zijn, anders wordt het dus over een paar weken. Voordeel is dan wel dat ik “Distributed Event Based Systems” en “Enterprise Ontology” allebei uitgelezen heb..

          Zie jij nog andere mogelijkheden?

          Groet,
          Rick

          Geplaatst op 25 augustus 2010 om 21:43 Permalink

  • Hoi Frans,

    Bedankt voor de tip! Ziet er inderdaad leuk uit. Eigenlijk een aanpassing om de queue ook meteen de event store te laten zijn, als ik het zo goed zie? Zit wel wat in, aangezien beide toch die events opslaan. Maar de api van een queue heeft wel weinig overlap met die van een event store. Hoe denk je daar zelf over?

    Groet,
    Rick

    Geplaatst op 04 augustus 2010 om 21:29 Permalink

  • Frans Bouker schreef:

    Kijk ook eens op http://queueunlimited.codeplex.com/, die mannen zijn daar zowel een voorbeeld applicatie aan het bouwen als een eigen datastore om de eventing goed te laten verlopen.

    Geplaatst op 04 augustus 2010 om 21:04 Permalink

  • Anatoly Zimakov schreef:

    @Jasper
    Over de synchrone communicatie gesproken. Inderdaad, deze communicatie kan je vaak niet ontwijken. Hoewel men ervoor kan zorgen dat de componenten van een systeem redelijk onafhankelijk van elkaar functioneren, kunnen de acties van sommige componenten afhankelijk zijn van de acties van andere componenten (afhankelijkheid in de tijd). Met EDA probeert men ook deze afhankelijkheden zoveel mogelijk te reduceren. Maar de event-driven aanpak is maar een instrument die je pas gaat gebruiken als je van de voordelen van dit instrument denkt te profiteren. In geval van EDA is de synchrone communicatie niet de sterkste eigenschap. Ik heb zelf nog niet heel veel ervaring met EDA, maar zoals ik het nu voor de ogen zie, op moment dat je event-driven aan de slag gaat, moet je er vanuit gaan dat je automatisch richting de asynchrone communicatie geduwd wordt. In sommige situaties profiteer je ervan. Maar als je toch perse een bepaalde mate van synchroniteit wil, moet je naar de oplossingen gaan zoeken. Wat dit betreft, probeert de CQRS-aanpak de uitkomst te bieden.

    Geplaatst op 08 maart 2010 om 11:09 Permalink

  • Jilles van Oossanen schreef:

    @Jasper
    Ik heb zelf geen ervaring met Haskell, daar kan ik dus weinig over zeggen. Maar het is natuurlijk wel interessant om te kijken welke taal nou het beste te gebruiken is om dit soort projecten in te realiseren. Zeker goed om dus een keer naar te kijken.

    Opdrachten zijn zeker niet altijd asynchroon. Dat is ook waar we nog steeds een beetje mee in onze maag zitten, maar we komen er steeds meer achter dat een (synchrone) koppeling tussen domain/repo en de reporting wel bruikbaar is. Denk hierbij bijvoorbeeld aan validatie (zie bovenstaande reactie van Rick). Verder wordt het belangrijkste hier toch wel synchroon afgehandeld, het tonen van data.

    Geplaatst op 08 maart 2010 om 10:17 Permalink

  • Jasper Stein schreef:

    Het concept om Commands en Queries van elkaar te scheiden doet me denken aan de functionele taal Haskell: daar zijn alle functies (lees: methode) in principe ‘pure’ – ze veranderen de state van je programma niet. Als je zij-effecten wil bereiken, moet je het type van die functie ‘inpakken’ in de IO monad: het typesysteem dwingt zo dus automatisch al een scheiding tussen command en query af. (Als ik de vergelijking tenminste zover mag doortrekken.)

    Overigens vraag ik me af of je wel altijd per se asynchroon wil werken. In het Echte Leven werken de dingen ook niet alleen door het sturen van berichten. Om een analogie te gebruiken: email [asynchrone communicatie] is handig, maar als ik snel iets moet weten pak ik de telefoon [synchrone communicatie].

    Als je dat dan combineert met het DDD-idee van een object repository als ‘verzamelplaats’ van objecten, kan je voor je reporting misschien een synchrone verbinding aanknopen met je repository.

    (Geen idee in hoeverre dit hout snijdt – dit alles van iemand die nog nooit van CQRS had gehoord, laat staan ervaring met CQRS of EDA)

    Geplaatst op 02 maart 2010 om 23:13 Permalink

  • @Jilles & de rest van het team

    Over het controleren van een uniqueness constraint.

    Greg zei zelf over het oplossen van uniqueness constraint dat hij dit zou doen door
    1) in de gui met een query een eerste validatie te doen en tonen aan de gebruiker
    2) in het domein op een service aan te roepen die vervolgens vergelijkt tegen het reporting model
    3) zolang het nog kan, de reporting database gewoon binnen de transactional boundary van de event write vanuit het domein te houden

    Als je dit alles combineert, dan is de kans zeer klein dat je twee users aanmaakt waarna vanuit het reporting model een compensating action genomen moet worden. Eigenlijk moeten dan twee van dat zoort acties threaded parallel gebeuren.

    Als je de transactional boundary naar alleen de eventstore trekt, dan nog is de kans zeer klein, maar dan kan de ‘user added’ event nog in de bus naar het reporting model zitten.

    Wat je dan alsnog kan doen is een klein beetje heuristiek toepassen: de laatst toegevoegde users in het domein bijhouden om tegen te kunnen vergelijken, in combinatie met de data vanuit de service die het reporting model uitleest.

    Wat te doen als een commando voor gebruiker toevoegen binnenkwam terwijl deze al bestaat is gewoon puur domeinlogica.

    Ik was het wel met Greg eens. :-)

    Geplaatst op 24 februari 2010 om 22:40 Permalink

  • Jilles van Oossanen schreef:

    Tijdens het onderzoek zijn wij ook op dit artikel van Udi Dahan gestuit. Hij beschrijft hier inderdaad dat het valideren van data overgelaten kan worden aan de GUI. Op deze manier moet je er dan voor zorgen dat je altijd commando’s verstuurd met valide data. En dit werkt natuurlijk voor een validatie van input. Wanneer er echter gechecked moet worden op dubbele waarden, kan dit voor problemen zorgen. In de GUI heb je namelijk alleen toegang tot de database. Hiervan is echter niet te zeggen dat deze altijd up-to-date is.

    Dit is natuurlijk een probleem wat zich altijd kan voordoen. Het probleem zit echter in het feit dat het bij een event-driven applicatie net iets vaker kan voorkomen, waardoor de database toch te onbetrouwbaar wordt voor dit soort validatie. Daarom is het toch nodig om extra validatie toe te passen, door bijvoorbeeld te checken of het event met de nieuwe data al bestaat in de event-store. Een andere mogelijkheid blijft natuurlijk om toch uit te gaan van de data in de database. Wanneer dan blijkt dat data dubbel is, zal deze simpelweg niet toegevoegd worden. De vraag blijft dan echter: Hoe ga je de gebruiker hiervan op de hoogte brengen?

    Geplaatst op 24 februari 2010 om 10:06 Permalink

  • David Perfors schreef:

    CQRS is erg nieuw en wordt erg veel genoemd in combinatie met events en event stores. Maar CQRS en event sourcing zijn twee los staande patterns. In princiepe kunnen je queries en commands gebruik maken van de zelfde data source. Event sourcing maakt het alleen mogelijk om te achterhalen wat er is gebeurd in het verleden.
    Wanneer je gebruik maakt van event sourcing en je moet een unique constraint uitvoeren, is het natuurlijk goed mogelijk om dit bij de commandhandler neer te leggen, of zelfs bij de user interface. Udi Dahan spreekt hier ook over. (hetzij in zijn artikel over CQRS hetzij in een presentatie)

    Geplaatst op 19 februari 2010 om 13:23 Permalink