Mogens Heller Grabe

  • Fed blogpost, og tak for det – jeg gjorde ikke så voldsomt meget ud af at snakke om fordele/ulemper ved event sourcing, så det er dejligt at du kommer ind på det.

    Efter min mening er der virkelig mange fordele ved event sourcing (og CQRS, som er det hack, der får event sourcing til at fungere i virkeligheden) – og fleksibiliteten i pro…[Read more]

  • I det forrige indlæg viste jeg Cirqus’ konfigurations-API, som helt objektivt set i høj grad kan klassificeres som “lækker”, måske endda “herre-nice”. Nu skal vi snakke lidt mere om event-processering – eller “projektioner”, som det også kaldes – altså det her, der sker med events efter at de er blevet genereret.

    “Projektioner” er i dette eksempel begrænset til views, altså materialiserede read-modeller i en database, men man vil formentlig også lave alle former for integration til downstream eksterne systemer via projektioner, f.eks. afsendelse af emails, events ud på en servicebus, dokumentgenerering, osv.

    Skalerbarhed

    CQRS-baserede løsninger bliver ofte fremhævet for deres unikke skalerbarhed, hvilket også kan være tilfældet, men det skal lige nævnes at denne skalerbarhed altså først og fremmest gør sig gældede på read-siden af systemet.

    Hvis man ønsker en konsistent domænemodel (og det gør man ofte, er min erfaring), så er flaskehalsen i system helt klart command-processeringen – alle commands må nødvendigvis behandles af en fuldt opdateret domænemodel, og emittede events skal serialiseres (pr. aggregate root som minimum, men også gerne serialisering på globalt niveau), hvilket har som konsekvens at der kan opstå contention omkring sekvensnummertildeling og efterfølgende gemning af events.

    Men da antagelsen er at command-processeringen er mindre belastet end læse-delen af de fleste systemer, så gør det ikke så meget – skalerbarheden kan dermed opnås ved at lade eventuelle projektioner opdatere sig asynkront efter processeringen af hver command. Klienter, som ønsker at læse, vil på den måde altid kunne læse data – de vil blot i nogle tilfælde læse noget, som er et par events bagefter virkeligheden.

    Et par events bagefter virkeligheden?

    Jeps, men det er ofte ikke noget problem – de fleste mennesker har alligevel vænnet sig til at de tal, der står på skærmen, har en vis staleness – vi har efterhånden fået uddannet brugerne til at forstå at tallene stammer fra det tidspunkt, hvor skærmbilledet blev indlæst, og at man skal trykke F5 eller lignende for at få opdateret værdierne såfremt der er foretaget handlinger andre steder i systemet.

    MEN der er alligevel et tidspunkt, hvor man som bruger ofte forventer at se “friske tal”, og det er når man selv har ændret noget. Hvis jeg f.eks. ændrer mit telefonnummer på min brugerprofil, og jeg efterfølgende ser det gamle nummer igen, så bliver jeg måske forvirret og forsøger at ændre det igen for at få systemet til at fatte det. Her vil det IKKE virke særlig intuitivt hvis view’et er et par events bagefter virkeligheden.

    Med andre ord, så vil man som bruger opfatte systemet som up-to-date hvis det blot altid viser effekten af egne ændringer, mens at effekten af andres ændringer godt må være lidt bagefter (“lidt” – ligesom i aller aller højest et par sekunder eller noget, men i praksis ofte 100 ms eller sådan noget).

    Hvordan gør man så det, når man arbejder med 100% asynkrone views?

    Asynkrone views

    Med Cirqus er alle views som udgangspunkt asynkrone. Konsekvensen af dette er at man, såfremt man skulle finde på at forespørge på et view umiddelbart efter processeringen af en command, formentlig ikke vil få fat i de ændringer, der måtte være sket som følge af den netop processerede command.

    Hvordan løser man så det?

    Det er sådan set ikke så slemt :) med Cirqus resulterer hvert kald til ProcessCommand i et CommandProcessingResult, som indeholder det højeste globale sekvensnummer for events emitted som resultat af den command, og med dette CommandProcessingResult i hånden kan man nu stille sig til at vente på at et eller flere specifikke views catcher up.

    Rent praktisk, så kan det gøres ved at man laver et ViewManagerWaitHandle, som man giver med til alle view managers – det kan eksempelvis se sådan her ud i et tilfælde, hvor jeg dedikerer 4 tråde til asynkron event-processering, hvoraf de tre “pools” af views vil registrere sig i vores waitHandle, hvilket medfører at vi har mulighed for at stille os til at vente på de views, som de styrer:

    var waitHandle = new ViewManagerWaitHandle();

    var commandProcessor = CommandProcessor.With()
    .EventStore(e => e.UseSqlServer(“mssql”, “Events”)
    .EventDispatcher(e => {
    e.ViewManagerEventDispatcher(waitHandle, enMasseViewsHer);
    e.ViewManagerEventDispatcher(waitHandle, nogleFlereViewsHer);
    e.ViewManagerEventDispatcher(waitHandle, endnuFlereViewsHer);
    e.ViewManagerEventDispatcher(evigtAsynkroneViewsHer);
    })
    .Create();

    Når tiden så kommer til at vi skal vente, så kan det gøres således:

    var result = commandProcessor.ProcessCommand(enEllerAndenCommand);

    await waitHandle.WaitForAll(result, TimeSpan.FromSeconds(3));

    for at “blokere” tråden indtil alle views som minimum har processeret de events, der kom ud af at processere enEllerAndenCommand (dog uden at blokere tråden rigtigt – læs evt. mere om C# async/await).

    Hvis jeg bare er interesseret i at vente på et bestemt view, så kan jeg gøre sådan her:

    await waitHandle.WaitFor<EtSpecifiktView>(result, TimeSpan.FromSeconds(1));

    hvilket ofte vil være hurtigere, dog afhængig af konfigurationen af views naturligvis.

    Nu bliver det rigtig sjovt

    Hvis man lige bruger 10 minutter på sit applikations-framework, så kan man skrue tingene ret lækkert sammen – sådan her kan man med Cirqus og et web-baseret system, der understøtter async/await, lave super-duper responsive og skalerbare systemer, som føles 100% konsistent og frisk for den enkelte bruger:

    command-processing

    Dvs. klienten skal efter processeringen af den første command og i resten af sin levetid blot huske det højeste globale sekvensnummer, som den selv har medvirket til at frembringe – i efterfølgende forespørgsler kan dette nummer så vedlægges, hvorefter man på server-siden kan stille sig til at vente på at dette nummer som minimum er blevet processeret af de relevante views. På denne vis parallelliserer man opdateringen af views med HTTP response og efterfølgende HTTP request, og i praksis vil der ret sjældent være egentlig ventetid i forbindelse med forespørgsler.

    Hvis man bygger denne mekanisme ind i applikationens framework og f.eks. bruger headers på HTTP requests til føre nummeret frem og tilbage, og f.eks. hooker await-kaldet ind i IoC containerens factory methods, som vi formoder står for injection af den enkelte IViewManager, så kan det gøres fuldstændig transparent for alle normale kodescenarier – med andre ord kan denne simulerede konsistens-dims bygges en gang for alle, og efterfølgende skal udviklere ikke tænke på eventual consistency i deres daglige arbejde.

    PS: Man kan vente på den enkelte IViewManager sådan her (hvilket naturligvis er hvad ViewManagerWaitHandle gør bag gardinerne): await viewManager.WaitUntilProcessed(result, timeout).

    Konklusion

    Dette indlæg er foreløbig det sidste i min CQRS-føljeton om praktisk CQRS i .NET med Cirqus. Jeg håber jeg har forklaret begreberne nogenlunde, og jeg håber at jeg har formået at kommunikere at anvendelsen af event sourcing og CQRS har nogle fantastiske fordele og at det ikke er så besværligt at bruge det i praksis, når blot man har et fornuftigt framework til at stille nogle fornuftige rammer til rådighed.

    Hvis man er interesseret i mere snak om event sourcing, CQRS og Cirqus, så kan jeg anbefale at man dukker op til et af de resterende to .NET user group events om emnet: AANUG eller CNUG (du kan desværre ikke nå at komme til ANUG– eller ONUG-arrangementerne – beklager…)

  • Nu hvor vi har fået sluttet cirklen og har fået beskrevet hvordan vi kan få Cirqus op at køre med view-generering, så vil jeg lige dvæle lidt ved initialiseringen – jeg viste det følgende kodeeksempel:

    // ved opstart:
    var eventStore = new MsSqlEventStore<TimeToBeCrushedView>(“mssql”, “Events”, automaticallyCreateSchema: true);
    var repository = new DefaultAggregateRootRepository(eventStore);

    var view = new MsSqlViewManager(“mssql”, “TimeToBeCrushed”,
    automaticallyCreateSchema: true);
    var dispatcher = new ViewManagerEventDispatcher(aggregateRootRepository, view);

    var commandProcessor = new CommandProcessor(eventStore, repository, dispatcher);

    // først….
    commandProcessor.Initialize();

    // og så i resten af applikationens levetid:
    commandProcessor.ProcessCommand(…);

    og det er helt klart en gyldig måde at instantiere CommandProcessor‘en på, men rent API-æstetisk så mangler der lissom noget… både i forhold til læsbarhed, men måske især i forhold til API discoverability – så derfor vil jeg anbefale denne smøre i stedet for:

    var view = new MsSqlViewManager<TimeToBeCrushedView&gt(“mssql”);

    var commandProcessor = CommandProcessor.With()
    .EventStore(e => e.UseSqlServer(“mssql”, “Events”))
    .EventDispatcher(e => e.ViewManagerEventDispatcher(view))
    .Create();

    som er fuldstændig ækvivalent med den anden. Der kan vist ikke være nogen tvivl om at nr. 2 er lidt mere lækker at se på – men hovedsagen er at man kan CTRL+SPACE sig vej igennem den, og afhængig af hvilke NuGet-pakker man har inkluderet, så vil der dukke flere extension methods op som konfigurationsmuligheder.

  • Nu hvor vi de foregående tre indlæg har arbejdet os hen til at kunne processere commands, så mangler vi bare det sidste trin i raketten for at kunne slutte cirklen: Views.

    Views dannes ud fra events efterhånden som de sker, og med Cirqus foregår dette ved at man installerer en passende IEventDispatcher implementation. En IEventDispatcher er en dims, der får mulighed for at gøre ting ved events efter at de er blevet gemt i vores event store, og dermed blevet en del af den evige og uforanderlige verdenshistorie.

    Man kunne forestille sig IEventDispatcher implementationer, der gjorde alle mulige spændende ting, f.eks. replikerede events til andre systemer, sendte events i en message queue i skyen osv., men i dette tilfælde vil vi prøve at danne et simpelt view, som holder styr på hvor lang tid det tager at mase hver rødbede, hvilket også er ret spændende.

    Med Cirqus er det ret nemt at lave simple views, der abonnerer på events og gemmer deres tilstand efter hver eneste event de processerer – man starter med at lave en klasse, som skal implementere IViewInstance<>-interfacet, som lukkes med en ViewLocator-implementation, som får betydning for hvor mange instanser, der bliver oprettet af vores view.

    I dette tilfælde vil vi bare have en view-instans per rødbede, og vi vælger derfor at lave view-klassen således:

    public class TimeToBeCrushedView : IViewInstance<InstancePerAggregateRootLocator>
    {
    public string Id { get; set; }
    public long LastGlobalSequenceNumber { get; set; }
    }

    Nu kan vi så implementere alle de ISubscribeTo<>-interfaces, som vi synes er interessante – i dette tilfælde vil vi gerne gemme tidspunktet for modtagelsen af den første BeetrootSquashed-event og så beregne time-to-be-crushed når vi modtager BeetrootCompletelyCrushed – derfor:

    public class TimeToBeCrushedView : IViewInstance<InstancePerAggregateRootLocator>,
    ISubscribeTo<BeetrootSquashed>,
    ISubscribeTo<BeetrootCompletelyCrushed>
    {
    public string Id { get; set; }
    public long LastGlobalSequenceNumber { get; set; }

    public void Handle(IViewContext context, BeetrootSquashed e)
    {
    }

    public void Handle(IViewContext context, BeetrootCompletelyCrushed e)
    {
    }
    }

    Nu skal vi så bare ordne det med tidspunkterne… MEN tid i et event sourcet system er et tricky emne, så vi kan IKKE bare fyre den af med DateTime.Now, for så får vi et andet resultat den dag vi sletter viewet og replayer alle events… det generelle princip med event sourcing er at ALT skal genereres ud fra vores events, så det skal vi også overholde når vi skal regne med tidspunkter.

    Heldigvis har DomainEvent-klassen en stribe headers, som bliver sat automatisk i forbindelse med at de bliver emitted, og det inkluderer også UTC-tidspunktet for hvornår det skete. Disse headers kan tilgås via Meta-property’en på DomainEvent-klassen, og til de mest almindelige headers (aggregate root-ID, sekvensnumre og tidspunktet) er der lavet nogle fine extension methods, som gør det nemt – den endelige version af vores view kan altså implementeres således:

    public class TimeToBeCrushedView : IViewInstance<InstancePerAggregateRootLocator>,
    ISubscribeTo<BeetrootSquashed>,
    ISubscribeTo<BeetrootCompletelyCrushed>
    {
    public string Id { get; set; }
    public long LastGlobalSequenceNumber { get; set; }

    public DateTime TimeOfFirstEvent { get; set; }

    public TimeSpan TimeToBeCrushed { get; set; }

    public void Handle(IViewContext context, BeetrootSquashed e)
    {
    // ignorere event hvis tidspunktet allerede er sat
    if (TimeOfFirstEvent > DateTime.MinValue) return;

    TimeOfFirstEvent = e.GetUtcTime();
    }

    public void Handle(IViewContext context, BeetrootCompletelyCrushed e)
    {
    TimeToBeCrushed = e.GetUtcTime() – TimeOfFirstEvent;
    }
    }

    Nu har vi kodet vores view – så mangler vi bare at give det liv, og det kan p.t. gøres i hukommelsen (vha. InMemoryViewManager, i SQL Server (vha. MsSqlViewManager) og i MongoDB (vha. MongoDbViewManager).

    Vi prøver at bruge SQL Server, ligesom vi også gjorde til vores events – lad os vende tilbage til opsætningen af vores command processor, hvor vi havde fat i en ViewManagerEventDispatcher, som er en IEventDispatcher-implementation, der kan administrere et vilkårligt antal views – den skal vi nu sætte op til at administrere vores TimeToBeCrushedView:

    var view = new MsSqlViewManager<TimeToBeCrushedView>(“mssql”, “TimeToBeCrushed”,
    automaticallyCreateSchema: true);
    var dispatcher = new ViewManagerEventDispatcher(aggregateRootRepository, eventStore, view);

    og dermed vil den fulde opsætning med events og view i SQL Server se således ud:

    // ved opstart:
    var eventStore = new MsSqlEventStore(“mssql”, “Events”, automaticallyCreateSchema: true);
    var repository = new DefaultAggregateRootRepository(eventStore);

    var view = new MsSqlViewManager<TimeToBeCrushedView>(“mssql”, “TimeToBeCrushed”,
    automaticallyCreateSchema: true);
    var dispatcher = new ViewManagerEventDispatcher(aggregateRootRepository, view);

    var commandProcessor = new CommandProcessor(eventStore, repository, dispatcher);

    // først….
    commandProcessor.Initialize();

    // og så i resten af applikationens levetid:
    commandProcessor.ProcessCommand(…);

    og med det har vi sluttet hele cirklen (= “cirkus” på latin, eller “cirqus” hvis det skal lugte lidt af CQRS) og kan nu knuse massevis af rødbeder og holde styr på hvor lang tid det tager at knuse hver enkelt rødbede.

    I næste indlæg vil jeg prøve at snakke lige mere om views – f.eks. vil jeg diskutere synkrone/asynkrone views, replay, osv.

  • I det forrige indlæg så hvordan vi kunne kode en Beetroot, som vi kunne sende en SqueezeCommand til. Når command’en blev processeret ville den kalde Squeeze(howMuch)-funktionen på rødbeden, som så ville emitte en […]

  • I det forrige indlæg beskrev jeg i grove træk principperne i event sourcing og CQRS, og så lovede jeg at give en introduktion til d60s Cirqus-framework… samtidig med introduktionen til Cirqus vil jeg også sørge for at vi har en fælles ordbog, sådan at vi er nogenlunde enige om hvad vi snakker om resten af tiden.

    Here goes:

    Domain-driven design

    Greg Youngs ide om CQRS udspringer af domain-driven design, altså DDD – faktisk blev konceptet, så vidt jeg ved, initialt omtalt som DDDD, hvilket er et akronym for “distributed domain-driven design”, altså DDD ophøjet til at indeholde en form for distribution.

    Eric Evans’ ideer om domain-driven design er således “bagt ind” i CQRS fra begyndelsen, og det er også herfra nogle af CQRS-termerne er hentet. I de følgende afsnit i dette indlæg vil jeg præsentere CQRS-relevante begreber og beskrive hvad der menes med dem.

    Aggregate root

    En aggregate root er roden i et aggregate: En samling af objekter, som logisk hører sammen. Roden er indgangen til aggregatet, og roden garanterer aggregatets konsistens og interne integritet. Roden ikke må gøre sig antagelser om andre rødders tilstand og skal være i stand til at operere selvstændigt og uafhængigt.

    Med Cirqus laver man en aggregate root i sin model ved at nedarve fra klassen AggregateRoot:

    public class Beetroot : AggregateRoot {
    public void Squeeze(decimal howMuch) {
    }
    }

    Command

    En command er et objekt, som beskriver en operation, der kan udføres på en aggregate root – dvs. både noget om hvilken operation, der skal udføres, samt alle eventuelle argumenter til den pågældende operation.

    Med Cirqus laver man commands ved at arve fra den generisk Command-klasse, som lukkes med den type af aggregate root, som command’en adresserer – bemærk desuden at base-klassen kræver en ID på en aggregate root samt en Execute-metode, som fungerer som en mapning fra command’en til et eller flere funktionskald:

    public class SqueezeCommand : Command {
    public SqueezeCommand(Guid aggregateRootId) : base(aggregateRootId) {
    }

    public decimal HowMuch { get; set; }

    public override void Execute(Beetroot aggregateRoot) {
    aggregateRoot.Squeeze(HowMuch);
    }
    }

    I dette tilfælde vil vores SqueezeCommand resultere i et kald til Squeeze-metoden på vores Beetroot. Så kan den lære det.

    Event

    Når rødbeden så bliver mast, så skal den benytte sig af Emit/Apply pattern, som er regelen for hvordan domænemodellen i et Cirqus-baseret system ændrer sin tilstand – det betyder at alle tilstandsændringer i en aggregate root udføres som konsekvens af påførelsen af et event, hvorfor vi må benytte et event til at ændre vores tilstand.

    Forestil dig at rødbeden holder styr på hvor mast den er vha. en decimal:

    public class Beetroot : AggregateRoot {
    decimal _squishiness = 1; //< just default to 1

    public void Squeeze(decimal howMuch) {
    // _squishiness -= howMuch; << NIXEN!!
    }
    }

    Nu siger Emit/Apply-regelen så at vi skal Emitte et event, og i den tilhørende Apply-metode må vi så ændre vores tilstand – det ser sådan ud:

    public class Beetroot : AggregateRoot, IEmit<BeetrootSquashed> {
    decimal _squishiness = 1; //< just default to 1

    public void Squeeze(decimal howMuch) {
    Emit(new BeetrootSquashed { HowMuch = howMuch });
    }

    public void Apply(BeetrootSquashed e) {
    _squishiness -= e.HowMuch; //< sådan der!
    }
    }

    Emit/Apply pattern ser måske lidt fjollet ud ved første øjekast, men det har den essentielle egenskab at alle tilstandsændringer i domænemodellen er forårsaget af events, og KUN events! – på den måde sikrer vi at events er fyldestgørende til at beskrive alt hvad der sker, hverken mere eller mindre!

    Og da events er sådan nogle små klumper af data, som kan gemmes i en database – i vores event store – så har vi faktisk alt hvad der skal til for at lave en model, hvis tilstand udelukkende er baseret på events.

    Lad os lige gøre rødbeden færdig, sådan at den gør verden opmærksom på det når den er fuldstændig mast:

    public class Beetroot : AggregateRoot,
    IEmit<BeetrootSquashed>,
    IEmit<BeetrootCompletelyCrushed> {
    decimal _squishiness = 1; //< just default to 1

    public void Squeeze(decimal howMuch) {
    if (howMuch <= 0) {
    var message = string.Format("Attempted to squeeze by {0}, but it must be positive!", howMuch);
    throw new ArgumentException(message, "howMuch");
    }

    // nothing happens when we're already crushed
    if (_squishiness <= 0) return;

    // emit crushed when we go below zero
    if (_squishiness – howMuch <= 0) {
    Emit(new BeetrootCompletelyCrushed());
    return;
    }

    // otherwise, just subtract how much
    Emit(new BeetrootSquashed { HowMuch = howMuch });
    }

    public void Apply(BeetrootSquashed e) {
    _squishiness -= e.HowMuch;
    }

    public void Apply(BeetrootCompletelyCrushed e) {
    _squishiness = 0;
    }
    }

    Nu har vi kigget på en AggregateRoot, en Command og en DomainEvent – i næste del vil jeg vise hvordan man med Cirqus kan bringe liv i vores Beetroot, sådan at vi kan bygge et system, der kan mase den ene rødbede efter den anden og samle vigtige stats i forbindelse hermed… stay tuned!

  • ThumbnailDa jeg har brugt den seneste tid dybt begravet i CQRS og event sourcing, og da der ikke p.t. på QED.dk findes artikler om emnet – og da vi hos d60 for nylig har udgivet vores egenudviklede CQRS + event sourcing […]

  • Jeg har en lillebitte ussel slat BTC ~ ca. 140 kr, som jeg købte vha. PayPal via VirWoX-exchangen og en eller anden fjollet Warcraft-valuta som mellemvej 🙂 jeg ved ikke om det er muligt nu, men på det pågældende […]

  • Kalder du mig “gammel”? 😉

    Og ja, helt sikkert – jeg er skam meget enig. Hvis man skal generalisere det lidt, så er det bare irriterende når folk er frelste og tror at deres yndlingsteknologi (ny eller gammel) […]

  • Fedt – godt sagt! Jeg bliver også irriteret når (primært unge) softwareudviklere kritiserer teknologier uden forståelse for de problemer, som den pågældende teknologi (bevist ved massiv empiri) har løst siden før […]

  • Haha, den med boxen er sgu genial! – de har virkelig tænkt ud af boxen da de fandt på at realisere metaforerne på den måde!

  • og ja, jeg kender helt sikkert også til følelsen af at være helt opbrugt – p.t. er jeg i den heldige position at jeg ikke har kunder og den slags, der utålmodigt står og hepper på mig, så lige for tiden plejer jeg […]

  • ThumbnailI Robert M. Pirsigs syrede roman, Zen And The Art Of Motorcycle Maintenance, beskriver hovedpersonen Phaedrus en række “mods-fælder” (“gumption traps”), hvoraf den farligste efter hans udsagn er “value rigidity” – […]