Jag och min kollega Marcus Olsson var uppe i Umeå förra veckan för att prata om CQRS och Event Sourcing på UmeJUG. Vi blev ett skönt gäng med många roliga diskussioner. Ett par dagar efter hemkomst dök det upp ett mail med lite följdfrågor kring ämnet. Jag delar gärna med mig av dessa intressanta frågeställningar. Och mina svar.

 1. En systemadministratör ändrar ett par fält på en order och skickar frågan till backend för att ändra tillstånd. Hur generas då kommandon?

Traditionellt sett är detta en vanlig syn på GUI-utveckling. Vi tänker i termerna CRUD. Jag läser upp en order, jag uppdaterar den och sedan skriver jag ned den i databasen. Det vi gör är att skicka DTO:er fram och tillbaka. En sak som vi vill uppnå i en CQRS- applikation är att fånga användarens intention vilket i många fall gör att vi behöver vända lite på hur vi tidigare tänkt.

Detta ställer även krav på vår GUI-design. Vi behöver skapa ett GUI där vi kan tyda vad användaren vill uppnå. Om vi återgår till orderexemplet. På en order kan man tänka sig att det finns ganska mycket data; tillstånd, varulista, kund, betalstatus, bestridande etc. och vad vi kanske naturligt hade gjort i ett traditionellt GUI är att vi läst upp orden, låtit användaren uppdatera de fält som fanns av intresse och sedan tillhandahålla en knapp “Save” som hade skickat tillbaka en DTO på den fullständiga orden till backend. Detta gör det i princip omöjligt att ta reda på vad användaren ville uppnå med sitt anrop.

Om vi istället för att fokusera på data, frågar oss; vad är det som användaren vill uppnå? En uppdatering av varulistan kan ske av många olika anledningar. Var det en retur? Har vi gett rabatt?

Om vi istället för att tänka CRUD, går över och tänker uppgifter kan vi generera kommandon som kapslar in användarens intention. Vi kan skapa en GUI-design som är logisk och bidrar till en bättre användarupplevelse. Istället för att visa och hantera data frågar vi istället efter intentionen direkt i GUI:t och tillhandahåller mindre vyer som tydligt illustrerar den uppgift som användaren kommit för att lösa.

Som användare vill jag kanske uppdatera ett flertal fält på en order. Här hjälper en uppgiftsbaserad design mig att hitta rätt. Jag kan först gå in och ändra leveransadress som genererar ett ChangeDeliveryAdressCommand. Jag väljer sedan att returnera en vara som genererar ett ReturnItemCommand. Det blir uppdelat i flera vyer men där varje vy tydligt illustrerar ett syfte. Resultatet blir ett flertal kommandon som tydligt kapslar in användarens intention som vi sedan kan släppa lös i vår domänmodell.

2. Vi vill inte att GUI:t ska bli segt och stå och hänga i väntan på svar. Hur löser man det?

I en CQRS-applikation delar vi upp vårt system i två delar – skrivning och läsning. Detta leder till att när vi skickat kommandot med användarens intention till vår backend, får vi endast tillbaka ett OK. Därefter måste vi skicka ytterligare en fråga för att hämta det uppdaterade tillståndet.

Beroende på krav, last och egna preferenser kan vi antingen låta skrivsidan vara synkron eller asynkron. Om skrivsidan är synkron kan vi hålla fast vid kommandot ända tills all affärslogik är exekverad och händelserna är publicerade på en meddelandekö, på väg till våra läsmodeller. En styrka med CQRS är att vi just kan vara asynkrona. Men nu kan det bli lite knepigt.

Från GUI:t tar vi reda på användarens intention och skapar ett kommando. Vår backend tar emot detta och lägger det på en kö och returnerar OK. Hur länge ska vi fråga vår lässida innan vi ger upp? Ska vi låta våra användare sitta och stirra på en progressbar?

I många fall kan vi lösa detta med smarta övergångar mellan våra vyer men om det skulle vara så att anropet tar tid kan vi alltid lösa problemet med hjälp av cachning. Vi ändrar tillståndet i GUI:t till förväntat tillstånd och låter en asynkron process hämta den uppdaterade läsmodellen. Det här beteendet stöter vi ofta på i kommentarsfunktioner på artiklar där vi lägger en kommentar.Vi ser att den dyker upp men ett par sekunder senare stuvas det om och det visar sig att det är någon som har kommenterat innan oss. Detta är en av bieffekterna men oftast något som vi kan leva med.

3. Läsmodeller underlättar rapportgenerering. Men rapporter är oftast konfigurerbara. Hur hanterar man detta?

På skrivsidan i en CQRS-applikation är databasen sanningen. Våra läsmodeller är endast projektioner av denna sanning. Detta gör att vi kan spinna upp olika läsmodeller för olika rapporter. Om vi också använder oss av Event Sourcing så kan vi generera nya läsmodeller med hjälp av historisk data.

Nyckeln här är historisk data. På skrivsidan exekverar vi affärslogik och resultatet är händelser som har skett. På lässidan kan vi inte påverka resultatet, det vore ologiskt då vår data kommer från dåtiden. Det gör att vi inte vill exekvera någon affärslogik eller ta beslut om datan i dessa händelser är logiska eller ej – det som har skett har skett. Vi vill endast tolka datan och uppdatera våra läsmodeller. Nu är vi alltså intresserade av CRUD!

Vi får även skilja lite på affärslogik och enklare aritmetiska termer. Vi har givetvis tillåtelse att te x. aggregera antalet lagda ordrar på en dag genom att endast lyssna på OrderPlacedEvent. Viktigt att tänka på är vi måste säkerställa att vi är idempotenta. Vi vill att hanteringen av händelser alltid ska ge samma resultat.

En rapport är konfigurerbar och vi har all tillåtelse att ställa en fråga som säger att vi endast vill ha delmängder av datan och låter klienten sköta genereringen/layouten av själva rapporten. Men om man nu har en situation där vi inte har tillräckligt med information på lässidan (vilket kräver exekvering av affärslogik). Hur gör man då? Jo, då kan vi använda oss av en saga som läser upp historisk data och genererar kommandon som konsumeras av vår domänmodell. Då genereras nya händelser med den data som är intressant. En denormaliserare på lässidan kan nu konsumera den nya händelsen och uppdatera vår läsmodell så att vår klient kan fråga efter den data som behövs för att kunna generera rapporten. Detta kan göras i exempelvis ett batch-jobb.

Låt oss lära mer av varandra när det gäller CQRS och Event Sourcing

På Citerus tycker vi att CQRS och Event Storming är väldigt intressant.  Jag vill verkligen uppmuntra till diskussion där vi kan lära av varandra. Har du frågor, kommentarer eller vill veta mer? Hör gärna av dig på [email protected] eller @daneidmark

Seminarium 6 oktober: Fånga affären med CQRS och Event Sourcing