View
2
Download
0
Category
Preview:
Citation preview
Examensarbete
Andreas Larsson
2012-06-05
Ämne: Datavetenskap
Nivå: G1E
Kurskod: 1DV40E
Command Query Responsibility
Segregation och Event Sourcing som
mjukvaruarkitektur
Abstrakt
I den här rapporten undersöks Command Query Responsibility Segregation (CQRS),
Event Sourcing och Task Based User Interface (händelsebaserat gränssnitt) som
tillsammans bildar en annorlunda arkitektorisk uppbyggnad av system. Tanken bakom
arkitekturen är att kunna spåra vad, när och hur data förändras i ett system samt
möjlighet att återskapa tidigare tillstånd, jämfört med traditionella system som endast
lagrar, hanterar och presenterar data i dess nuvarande tillstånd. Undersökningen har
gjorts genom att ta fram en bokningsprototyp och resulterade i för –och nackdelar,
användningsområde och hur CQRS, Event Sourcing och händelsebaserat gränssnitt kan
implementeras tillsammans.
Abstract
In this report examines Command Query Responsibility Segregation (CQRS), Event
Sourcing and Task-Based User Interface, which together forms a different architecture
when building systems. The idea behind the architecture is to be able to track what,
when and how data changes in a system, and also the ability to recreate a previous states.
This can be compared to traditional systems which only stores, manages and presents
data in its current state. The poll was conducted by producing a booking prototype
system, resulting in the advantages and disadvantages of the architecture, when to use it
and how CQRS, Event Sourcing and Task Based User Interface can be implemented
together.
Förord
Denna rapport skrevs som ett avslutande examensarbete inom datavetenskap för
programmet Webbprogrammerare på Linnéuniversitetet i Kalmar. Idén till arbetet kom
från företaget DotNet Mentor i Göteborg.
Jag vill passa på att tacka min handledare på universitetet, Mats Loock, för hjälp med
framställandet av denna rapport. Jag vill även tacka utvecklarna på DotNet Mentor,
framförallt Mikael Egnér och Kristoffer Ahl, för uppdraget och för all hjälp och goda
råd.
Innehållsförteckning
Abstrakt ................................................................................................................ I
Abstract ................................................................................................................ I
Förord ..................................................................................................................II
1. Bakgrund ...................................................................................................... 1
1.1 Introduktion ..................................................................................................................... 1
1.2 Frågeställning ................................................................................................................... 4
1.3 Avgränsningar .................................................................................................................. 4
2. Metod ........................................................................................................... 6
2.1 Metoddiskussion .............................................................................................................. 6
3. Genomförande .............................................................................................. 7
4. Resultat ........................................................................................................ 8
4.1 Implementation av CQRS, Event Sourcing och Task Based UI ............................. 8
4.1.1 Domain Driven Design och Aggregate Roots .................................................. 8
4.1.2 Commands .............................................................................................................. 9
4.1.3 Command Handlers ............................................................................................. 11
4.1.4 Events .................................................................................................................... 12
4.1.5 Event Handlers ..................................................................................................... 12
4.1.6 Task Based UI....................................................................................................... 13
4.1.7 Sagas ....................................................................................................................... 14
4.2 Flöde CQRS och ASP.NET MVC ............................................................................. 16
5. Analys ......................................................................................................... 18
5.1 Fördelar ........................................................................................................................... 18
5.2 Nackdelar ........................................................................................................................ 20
6. Diskussion och slutsats .............................................................................. 21
7. Källförteckning ........................................................................................... 24
8. Bilagor ........................................................................................................ 26
8.1 Bilaga 1: <Exempel på aggregatrot med entiteter> ................................................. 26
8.2 Bilaga 2: <Lagring av bokningsöversiktsdata i läsdatas>........................................ 27
1
1. Bakgrund
1.1 Introduktion
Många system, webbsidor och applikationer erbjuder användare möjligheter att,
förutom att läsa information, även förändra information, alltså i grunden data. En
förändring av data innebär att en användare antingen lägger till, förändrar befintlig
eller tar bort data. Data lagras oftast i någon form av databas, och det traditionella
sättet för ett system att hantera förändringar av data är genom CRUD (Create, Read,
Update, Delete), alltså skapa, hämta, uppdatera och ta bort data. När en användare av
ett system på något sätt utför en handling som påverkar data, kommer förändringen
att genomföras i databasen och nästa gång samma data efterfrågas kommer det
förändrade tillståndet att presenteras.
I en del fall är det kanske det interaktionen handlar om, att förändra och hämta data,
men de flesta system är byggda för att hantera någon form av affärsverksamhet. För en
användare som ändrar sin adress innebär det att denne just ändrar sin adress, men för
en affärsverksamhet kanske det innebär; ”-Var adressen felaktig eller har personen
flyttat och vi ska skicka ut ett erbjudande?”.
Ur affärssynpunkt finns det alltså tillfällen då ägaren av ett system inte bara vill veta
nuvarande tillstånd på data utan även hur, när och varför det blivit så. För att ta det ett
steg längre, varför inte erbjuda ägaren av ett system att kunna spåra alla händelser som
sker och möjligheten att återskapa ett tidigare tillstånd.
Med ett traditionellt CRUD – system, som lagrar data i dess nuvarande tillstånd, blir
det svårt att erbjuda ovan nämnda tjänster. För att kunna möta det behovet måste
utvecklare tänka annorlunda. Data i en databas ser troligtvis ut som det gör för att
användare av ett system har genomfört en eller flera händelser i gränssnittet, exempelvis
klickat på en knapp, som i sin tur kommer att förändra data i databasen, exempelvis en
adress. Om det är händelser som styr tillståndet av data, varför inte lagra dessa istället?
Event Sourcing ser till att alla förändringar i ett system fångas som en sekvens av
händelser (Events), vilket innebär att händelseobjekt lagras i den ordning som de sker.
2
Genom att köra igenom alla händelseobjekt kan nuvarande tillstånd på data skapas,
och förutom att systemet vet exakt vad som hänt, är det möjligt att återskapa ett
tidigare tillstånd av systemet [1].
Att lagra händelser öppnar upp möjligheter för att spåra exakt vad som hänt, när och i
vilken ordning, men varför en händelse har skett är fortfarande oklart. Som nämndes i
exemplet tidigare; om det är viktigt för en affärsverksamhet att skilja på om
användaren ändrar en felaktig adress och att ändra adress för att denne har flyttat
behöver systemet kunna hantera användarens intentioner, det vill säga kunna spåra vad
användaren har i åtanke när denne gör en förändring. I ett traditionellt CRUD -
baserat gränssnitt, det vill säga låta användaren klicka på en ”ändra-knapp” och sedan
ändra i ett eller ett par fält och klicka på spara, är det svårt att spåra användarens
egentliga avsikt. Genom att istället erbjuda ett så kallat uppgiftsbaserad gränssnitt, Task
Based User Interface, följer man användarens intentioner och låter denne utföra
kommandon som representerar olika avsikter [2]. Att ändra en felaktig adress och att
användaren har flyttat innebär i slutändan samma resultat, det vill säga en ändrad
adress, men genom att skicka kommandona ”CorrectMisspelledAddress” eller
”MoveCustomerToNewAddress” till systemet kan man spåra varför saker och ting
sker.
I ett system presenteras vanligtvis nuvarande tillståndet av data för användaren, och att
”spela upp” alla lagrade händelser enligt Event Sourcing för att visa nuvarande
tillståndet är inte speciellt effektivt. I ett CRUD – system används oftast, för lagring av
data, en relationsdatabas med flera tabeller som oftast har kopplingar sinsemellan. Att
exempelvis visa en kundfaktura innehållandes kunduppgifter, namn på köpta
produkter, pris och så vidare innebär att data sammanfogas från flera olika tabeller.
För att presentera data från olika tabeller används ofta någon form av ORM (Object
Relational Mapper) och modellklasser med egenskaper som representerar kolumner i
tabellerna i databasen. Ibland konverteras data till vymodellkasser och i en del fall sker
beräkningar innan data ska presenteras. Koden är oftast separerad i olika ”lager” så
som dataåtkomstlager, affärslogiklager och presentationslager. Alla dela fyller sin
funktion väl när data ska förändras, men varför gå igenom alla dessa steg för att
presentera data? Varför inte bara hämta data och visa den?
3
Command, Query, Responibility, Segregation (CQRS) är ett designmönster där man delar
upp läsning från skrivning och låter de vara oberoende av varandra. Uppdelningen
beror på om en handling i ett system är ett kommando (command), det vill säga en
metod som kommer att förändra eller lägga till data, eller om handlingen är en fråga
(query), det vill säga en metod som returnerar data. Genom att dela upp och ta bort
beroendena mellan skriv och läs finns möjligheten att på ”lässidan” lagra data enligt
devisen en tabell per vy, det vill säga att data lagras på det sättet det ska presenteras [2]
[3].
När utvecklare pratar om CQRS - arkitektur är det vanligt att de menar CQRS
tillsammans med Event Sourcing och Task Based UI. Tillsammans bildar de ett flöde,
väldigt förenklat, enligt Figur 1.
Figur 1: Arkitektonisk översikt CQRS med Event Sourcing
Via ett händelsebaserat gränssnitt (Tasked Based UI) utför användaren av systemet ett
kommando (Command) som har för avsikt att förändra tillståndet på data.
Kommandot valideras, det vill säga ser till att data är i korrekt format, innan en eller
4
flera metoder i domänmodellen anropas. Domänobjektets nuvarande tillstånd
återskapas genom att spela upp alla tidigare händelser (Events) kopplade till
domänobjektet. Domänmodellen har hand om affärslogik, till exempel kontrollera om
kunden har gjort fler än fem beställningar och i så fall erbjuda kunden rabatt.
Domänen utför det kommandot sa till den att göra och skapar händelser som
återspeglar vad som hänt och lagrar dessa i en händelsedatabas (Event Store).
Händelserna skickas också till en ”Event bus” för att tas omhand av händelsehanterare
(Event Handlers) som har i uppgift att uppdatera läsmodellerna i läsdatabasen (Read
Databases). När användaren av systemet frågar efter information hämtas data direkt
från läsdatabasen, där data lagras precis som det ska presenteras. Med andra ord sker
inga sammanfogningar och beräkningar tabeller emellan.
Termen CQRS (tillsammans med EventSourcing) dök upp ungefär runt 2009 och har
på senare år blivit lite av ett så kallat ”buzzword”, där en del förespråkar
designmönstret starkt medan vissa menar på att man skapar komplexitet där det inte
fanns förut. Avsikten med denna rapport är att undersöka hur det är att bygga ett
system enligt CQRS, vilka för- och nackdelar, vilka möjligheter det skapar, hur man
går tillväga för att spåra användarens intentioner och vad denne gör i systemet.
1.2 Frågeställning
Frågeställningar som kommer att undersökas närmare är:
Hur används och vilka för –och nackdelar jämfört med ett traditionellt system
innebär det att använda CQRS-arkitekturen?
Hur hanteras data?
På vilket sätt är det möjligt att spåra vad användaren gjort i systemet och ta
fram rapporter över det?
Är en implementation av CQRS komplex?
1.3 Avgränsningar
Då arbetet är tidsbegränsat kommer fokus att ligga på att undersöka grundläggande
delar i arkitekturen. Det är känt att CQRS inte är applicerbart för alla typer av system,
och undersökningen kommer att göras på ett system där det troligtvis skulle vara
5
användbart. Hade undersökningen gjorts på ett system som inte alls passar med CQRS
resultatet troligtvis blivit annorlunda.
6
2. Metod
För att få svar på frågeställningarna kommer en prototyp i form av ett bokningssystem
för färjor att utvecklas. Ett bokningssystem består av två delar, själva bokningsdelen
för kunder och ett administrationssystem. Då ett av kraven på administrationsdelen
kommer att vara att kunna spåra vad användare gör i systemet, måste gränssnittet för
kunderna vara ett Tasked Based UI som skickar olika kommandon beroende på vad
kunden gör. Bokningssystem är också bra för att se hur man i CQRS kan hantera hur
en händelse påverkar en annan del av systemet, t.ex. att genomföra bokningen
kommer, förutom att påverka bokningen, att påverka antalet bokade passagerare på
berörd avgång.
De praktiska erfarenheterna som prototypen förhoppningsvis ska inbringa kommer
att vävas ihop med andra personers slutsatser och förhoppningsvis ge svar på
frågeställningarna som rapporten tar upp. Det finns väldigt många diskussionsgrupper
på internet där det råder delade meningar om vad CQRS är, när det ska användas och
hur det ska implementeras, och rapporten kommer att sträva efter att följa vad Young
[2] och Dahan [4], som var de första att nämna CQRS - mönstret, anser.
2.1 Metoddiskussion
Enligt förespråkarna av CQRS är det inte till att användas för alla system, Abdullin
menar att det passar för mindre än 20% av fallen [4]. Att veta när det passar in och
inte är svårt att veta på förhand, ett bokningssystem kanske inte alls är rätt ställe att
använda det på, vilket kan ge en orättvis bild av resultatet om det visar sig att det inte
alls passar. Däremot innehåller ett bokningssystem ”samarbete” mellan flera
användare, det vill säga att fler än en person arbetar mot samma data (flera personer
kan göra bokningar på samma avresa samtidigt). Detta är enligt Dahan ett av
grundkraven för att använda CQRS [5].
7
3. Genomförande
Då CQRS, Event Sourcing och Task Based UI är mycket att läsa in sig på under
begränsad tid, föll valet på att använda ett ramverk för att underlätta utvecklingen av
bokningsprototypen. ”SimpleCQRS” 1, som är ett litet CQRS – ramverk för .NET, har
använts tillsammans med Jonathan Olivers ”EventStore” 2 för att underlätta arbetet
med CQRS – arkitekturen och lagring av händelser.
Ovanpå det har två ASP.NET MVC 4 3 - projekt skapats för webbgränssnittet, en för
bokningsdelen för kunder samt en för administrationsdelen. För att hantera
realtidsuppdateringar , alltså uppdatera data på användarnas skärmar då det sker
förändringar på servern, har biblioteket SignalR 4 använts.
För lagring av data föll valet på att använda en dokumentbaserad databas, framförallt
för att läsdatabasen ska lagra data på det sättet som det ska visas. RavenDB 5 är en
dokumentdatabas som lagrar data i formatet JSON, som är väl integrerat med .NET
vilket gjorde att det var smidigt att arbeta med. Lagring av händelser sker också i
RavenDB. För hantering av inloggning och användardata användes Microsoft SQL
2008 6.
Utöver framtagande av en prototyp har mycket av tiden ägnats åt att undersöka hur
andra har använt sig av CQRS och vad de dragit för slutsatser. Det pågår fortfarande
många diskussioner på forum och bloggar där utvecklare har delade meningar, och att
ta in och tolka vad som kan ses som korrekt eller inte har tagit stor del av tiden.
1 https://github.com/tyronegroves/SimpleCQRS 2 https://github.com/joliver/EventStore 3 http://www.asp.net/mvc/mvc4 4 http://signalr.net/ 5 http://ravendb.net/ 6 http:// microsoft.com/sqlserver/
8
4. Resultat
Följande kapitel tar främst upp hur CQRS, Event Sourcing och Task Based UI
implementeras. Det visade sig vara svårt att hitta bra kodexempel, men utvecklingen av
systemet har försökt att följa teorierna kring arkitekturen så noga som möjligt.
Kodexempel som förekommer i kapitlet är till viss del kopplade till ramverket
SimpleCQRS, men principerna är desamma oavsett ramverk eller om utveckling sker
utan ramverk.
4.1 Implementation av CQRS, Event Sourcing och Task Based UI
4.1.1 Domain Driven Design och Aggregate Roots
För att överhuvudtaget kunna använda sig av CQRS, Task-Based UI och Event
Sourcing krävs att man använder Domain Driven Design (DDD) [2]. Kortfattat
innebär DDD att, istället för att modellera data, modelleras beteenden (metoder) som
återspeglar domänlogiken, det vill säga affärsverksamheten [2] [6]. Figur 2 beskriver
skillnaden på hur modellen för en kund kan se ut i DDD jämfört med CRUD.
Figur 2: CRUD-modell och DDD-modell på en Customer
9
Inom DDD finns något som kallas ”Aggregates”, aggregat, som är en samling
associerade objekt som behandlas som en enhet [7]. I exemplet i Figur 2 hade, vilket
också är vanligt i en relationsdatabas, kund och dennes adress kunnat hanterats som
två olika entiteter, Customer och Address. En adress är kopplad till en kund, det vill säga
det finns ingen mening för en adress att existera utan koppling till en kund, vilket
innebär att Customer och Address kan ses som en aggregat och att Customer är dess
”Aggregate Root”, aggregatrot. Customer kommer således att exempelvis ha metoderna
AddNewAddress och MoveCustomerToNewAddress för hantering av adresser. DDD säger
också att det endast är aggregatrötterna som utomstående objekt tillåts att hålla
referenser till [7], vilket innebär att det är aggregatrötternas metoder som anropas från
kommandohanterarna när ett kommando skickas i systemet.
I ett system för bokning av passagerare och fordon på färjor kommer Reservation att
vara aggregatrot och bilda aggregat med bland annat entiteterna Passenger och Vehicle,
eftersom det inte finns någon mening med att passagerare och fordon existerar utan
att finnas på en bokning. Reservation kommer att innehålla metoder (hantera beteenden)
så som AddPassengersToReservation och AddVehiclesToReservation. Bilaga 1 visar hur en
aggregat med entiteter kan se ut.
4.1.2 Commands
Ett kommando är en uppmaning som har för avsikt att förändra tillståndet i systemet,
varje kommando tas om hand av en händelsehanterare (command handler). Vanligtvis
skapas ett kommando då det sker en Post, Update eller Delete i systemet, exempelvis vid
postning av ett formulär. Kommandot namnges i imperativ, exempelvis
AddPassengersToReservation, eftersom kommandot uppmanar systemet att förändra något.
Figur 3 visar hur ett kommando kan se ut vars avsikt är att lägga till passagerare på en
bokning.
Figur 3: Exempel på ett kommando
10
Endast data som är nödvändig för att utföra kommandot skickas, i Figur 3 är det
endast en lista med passagerare som behövs, samt Id på reservationen. I en CRUD –
applikation hade det varit nödvändigt att skicka med all data som har med en
reservation att göra, eftersom modellklasserna endast innehåller egenskaper som
representerar kolumner i tabeller i databasen. Detta medför att om inte all data skickas
med, kommer vissa kolumner i databasen att bli null.
Innan ett kommando skickas ska det vara validerat, att det innehåller korrekt format av
data, vilket vanligtvis i ett första steg görs direkt på klienten, dock även på servern. Ett
kommando ska inte misslyckas för att data som skickas inte är i korrekt format. Figur
4 visar hur validering sker med ASP.NET MVC, innan ett kommando skickas.
11
Figur 4: Exempel på implementering av validering
Om kommandot är validerat skickas det iväg asynkront och tas emot av en
kommandohanterare. Enligt Dahan är ett bra kommando då systemet kan svara ”Tack!
Ditt bekräftelsemail kommer att anlända inom kort” [8]. Ett kommando kommer med
andra ord aldrig returnera något, vilket gör att de kan skickas asynkront och låta
kommandot utföra dess uppgift i bakgrunden. I Figur 4 returneras null, anledning till
det är att en händelsehanterare kommer att uppdatera gränssnittet när kommandot
genomfört förändringarna, vilket förhoppningsvis sker på några millisekunder. Istället
för null skulle enligt Dahan ett ”Tack-meddelande” visas för användaren.
4.1.3 Command Handlers
Kommandohanterare tar emot kommandon och anropar metoder på aggregatrötterna
(domänen) som genomför de förändringar i domänen som kommandot beordrar, det
vill säga publicerar händelser. Figur 2 visar hur metoder på aggregatrötter kan se ut.
12
4.1.4 Events
Händelser representerar förändringar i ett systems tillstånd, och publiceras från metoder i
domänen/aggregatrötterna, Figur 2 visar exempel på det. Ett eller flera händelseobjekt
representerar de förändringar som ett kommando vill utföra, med andra ord; händelser
är en bekräftelse på att ett kommando har utförts.
Händelser anges i preteritum, exempelvis PassengersAddedToReservation, för att tydliggöra
att någonting hänt. Dessa tas om hand av händelsehanterare (Event Handlers). Figur 5
visar hur ett händelseobjekt kan se ut.
Figur 5: Exempel på en händelse
Oftast är ett händelseobjekt en kopia på kommandoobjektet, jämför Figur 3 och Figur
5 (ReservationId är satt till AggregateRootId i DomainEvent som alla händelser ärver
ifrån i ramverket SimpleCQRS, se Figur 2 för exempel på det). Det är
händelseobjekten som lagras i händelsedatabasen när Event Sourcing används.
4.1.5 Event Handlers
Händelsehanterare ser till att uppdatera alla ”vyer”, alltså läsmodellerna, i läsdatabasen
som en händelse påverkar, det vill säga uppdatera data som presenteras för
användaren. I en del fall innebär det att flera läsmodeller måste uppdateras beroende
på om en händelse påverkar data i flera ”vyer”.
Ska exempelvis ett bekräftelsemail skickas till en användare är det i eventhanteraren
dessa skickas. Figur 6 visar hur en läsmodell uppdateras och hur en metod på en hub
anropas för att uppdatera data på användarens skärm utan omladdning av sidan.
13
Figur 6: Exempel på händelsehanterare
4.1.6 Task Based UI
För att kunna spåra vad användaren gör i systemet krävs ett Task Based UI. Task
Based UI är vanligt förekommande, Windows 7 använder sig mycket av det. Figur 7
visar ett exempel på hur ett Task Based UI ser ut i Windows 7.
Figur 7: Exempel på händelsebaserat gränssnitt i Windows 7
Istället för att endast erbjuda en ”ändra-knapp” är det möjligt att spåra användarens
intentioner genom att erbjuda användaren olika alternativ för att utföra olika uppgifter.
Figur 8 visar hur man med ett Task Based UI kan spåra om användaren vill ändra
adress för att denne har flyttat, eller om det är för att korrigera en felaktig adress.
14
Figur 8: Händelsebaserat gränssnitt för att redigera adress
Båda alternativen kommer att skicka samma data till servern, men via två olika
kommandon, som i sin tur via metoder på domänmodellen kommer att publicera två
olika händelseobjekt, vilket gör att systemet vet exakt vad som hänt och varför. På så
sätt är det möjligt att skilja på om en kund har ändrat en felaktig adress eller har flyttat
och exempelvis skicka ut ett välkomstmeddelande eller erbjudande till nyinflyttade.
4.1.7 Sagas
En saga är till för att hantera ”long-running business transaction” under en
förutbestämd tid [4]. Sagas lyssnar på händelser och skickar kommandon, till skillnad
från aggregater som tar emot kommandon och publicerar händelser [9].
Ett bokningsförfarande är ett exempel på en sådan process. När en användare väljer
vilka platser, alternativt hur många personer, låses de platserna oftast tillfälligt i ett
förutbestämt antal minuter. Anledningen till att platser låses och under en viss tid är
för att, när bokningen väl ska bekräftas, hantera att inte användarens platser har bokats
upp av någon annan under tiden användaren genomfört bokningen. Om användaren
inte bekräftat bokningen under den bestämda tiden kommer bokningen att
ogiltigförklaras och platserna kommer återigen att vara tillgängliga för andra användare
att boka.
15
Följande scenario kan anses vara rimligt i en bokningsprocess för att boka plats på en
färja. För exemplets skull hålls det enkelt utan val av hytter och dylikt. Publicerade
händelser anges i kursiv.
1. Kunden anger antal passagerare och antal fordon och söker på en avgång,
ReservationCreated, PassengersCountPreliminaryAddedToDeparture,
VehiclesCountPreliminaryAddedToDeparture
2. Kunden väljer avgång: DepartureAddedToReservation,
3. Kunden anger passageraruppgifter: PassengersAddedToReservation
4. Kunden anger registreringsnummer till fordon: VehiclesAddedToReservation
5. Kunden anger kunduppgifter: CustomerAddedToReservation
6. Kunden bekräftar bokning: ReservationConfirmed
7. Kunden betalar bokningen: ReservationPayed
8. Systemet kontrollerar betalning och ”markerar” bokningen som betald och
genomförd och skickar ett bekräftelsemail till kunden: ReservationCompleted,
PreliminaryPassengersCountMadeDefinitely, PreliminaryVehiclesCountMadeDefinitely,
ConfirmationEmailToCustomerSent
Bokningsprocessen sker genom åtta olika steg som sträcker sig över en viss tid, en
”long-running business transaction”. Processen påverkar två olika aggregatrötter,
Reservation och Departure. CQRS säger att ett kommando endast kan påverka en
aggregatrot, men att göra en bokning påverkar antalet bokade på en avgång vilket gör
att en saga krävs.
Som nämndes tidigare är det vanligt att ett bokningssystem preliminär låser antalet
angivna platser i ett förutbestämt antal minuter och har inte bokningen genomförts
ogiltigförklaras den. Genom att implementera en saga som lyssnar på alla händelser i
bokningsprocessen är det möjligt att sätta en ”time out”. När ReservationCreated
publiceras startas sagan och då användaren angett antal passagerare och fordon skickar
sagan kommandon till Departure - aggregaten som i sin tur publicerar
PassengersCountPreliminaryAddedToDeparture, VehiclesCountPreliminaryAddedToDeparture
(dessa görs för att låsa antalet platser och se till att inte avgången blir fullbokad under
tiden bokningen genomförs). När sagan startas sätts en ”time out” på 20 minuter, har
inte ReservationConfirmed (och övriga händelser som krävs) publicerats inom 20 minuter
16
kommer kommandona CancelReservation, RemovePreliminaryPassengersFromDeparture och
RemovePreliminaryVehiclesFromDeparture att skickas.
Vanligtvis blir en bokning inte genomförd förrän en betalning har inkommit. Då sagan
lyssnar på händelsen ReservationPayed kan sagan skicka kommandona CompleteReservation,
MakePreliminaryPassengerCountDefinitly, MakePreliminaryVehiclesCountDefinitly och
SendConfirmationEmailToCustomer. Skulle inte ReservationPayed inkommit efter, säg 30
minuter efter att händelsen ReservationConfirmed publicerats, körs kommandot
CancelReservation och bokningen blir ogiltigförklarad.
4.2 Flöde CQRS och ASP.NET MVC
Användargränssnittet anropar en metod på en controller som antingen kan returnera
en vymodell och skicka till användargränssnittet, se Figur 9, eller skicka ett
kommando, se Figur 10.
Figur 9: ASP.NET MVC Controller som returnerar en läsmodell från läsdatabasen
Figur 10: ASP.NET MVC Controller som skickar kommando
17
När kommando skickas kommer en ”command bus” att ta emot och validera
kommandot. Om valideringen går igenom kommer ”command bus” att delegera
vidare kommandot till rätt kommandohanterare som i sin tur kommer att anropa en
metod på aggregatroten och skicka med data från kommandot (ramverket
SimpleCQRS hjälper till med delegeringen från ”command bus” till
kommandohanteraren).
Metoden på aggregatroten publicerar en eller flera händelser med data från
kommandot som representera ändringarna som sker i systemet till följd av
kommandot som skickats. På samma sätt som kommandohanteraren tar om hand om
kommandon tar händelsehanteraren hand om händelser (SimpleCQRS hjälper till med
detta också), vars huvudsyfte är att uppdatera alla läsmodeller, ”vyer”, i läsdatabasen
som påverkas.
Notera att i Figur 10 returneras null från controllern om ett kommando skickas. Ett
kommando returnerar aldrig någonting. Istället för att returnera null är det vanligt att
omdirigera användaren till en konfirmationsmeddelande, men i exemplet i Figur 10
sker ett ajax-anrop och när händelsen har publicerats kommer användargränssnittet att
anropas från händelsehanteraren och uppdatera det med ett bekräftelsemeddelande.
18
5. Analys
I detta kapitel analyseras de för- och nackdelar om CQRS - arkitekturen som
undersökningen har inbringat.
5.1 Fördelar
CQRS öppnar upp nya möjligheter och förenklar utveckling där det traditionellt sett
kan vara komplext. Med komplext menas inte komplex domänlogik utan teknisk
komplexitet, genom att dela upp läs från skriv försvinner stora delar utav den tekniska
komplexiteten. För att presentera något hämtas data direkt från läsdatabasen och
presenteras, i ”SQL – termer”; SELECT * FROM TABLE WHERE ID = ID, med
andra ord inga sammanslagningar av tabeller och beräkningar eller dylikt som
komplicerar. När affärsverksamheten efter en tid kommer på att ”vi måste presentera
det här värdet här”, kan det ofta, traditionellt sett, innebära att utvecklare måste skriva
om domänmodellerna, i värsta fall ändra i databasen och sedan uppdatera associerad
kod. Med CQRS och Event Sourcing behöver bara händelsehanteraren som påverkas
lägga till värdet i aktuell läsmodell, därefter spela upp alla tillhörande händelser och
värdet är på plats i läsmodellen i läsdatabasen.
Läsdatabasen lagrar data på det sätt det ska presenteras, vilket innebär att relationer
mellan tabeller i databasen inte existerar. Eftersom relationer inte existerar är det inte
nödvändigt att använda sig av en relationsdatabas, exempelvis Microsoft SQL Server. I
prototypen som utvecklades användes en dokumentdatabas, RavenDB, som lagrar
data i formatet JSON i dokument. Dokumentdatabaser strävar efter att hämta data så
effektivt som möjligt, vilket gör att de passar bra som läsdatabaser. Bilaga 2 visar hur
lagringen av vyn ”ReservationOverview”, alltså en bokningsöversikt för en kunds
bokning, lagras i RavenDB.
Att dela upp läs från skriv gör det också möjligt att skala dessa oberoende av varandra
och dela upp de på olika servrar, exempelvis låta läsdatabaserna ligga på tio servrar för
snabb åtkomst av data, medan skrivdatabasen kan ligga på en server. Problemet som
uppstår är att se till att alla läsdatabaser blir uppdaterade när det sker förändringar,
19
beroende på verksamhet är det mer eller mindre viktigt att läsdatabaserna är
synkroniserade samtidigt.
Att lagra allt som sker i ett system, Event Sourcing, innebär förutom att veta hur och
när data förändras även varför. Eftersom Event Sourcing lagrar allt som sker i ett
system bidrar det till en automatisk logghistorik. Tillsammans med ett väl utfört Task
Based UI får affärsverksamheten exakt koll på vad som har hänt, det vill säga ett fullt
spårningsbart system. Med Event Sourcing blir det också lättare att hitta och spåra
buggar i ett system, eftersom allt som skett har lagrats.
Med Event Sourcing finns möjligheten att ta fram nya, intressanta och informativa
rapporter som inte affärsverksamheten hade planerat från början. Exempelvis kommer
affärsverksamheten bakom bokningssystemet för färjor på att de behöver ta fram
rapport i vilket steg som det är vanligast att användare avbryter bokningen. Eftersom
en boknings byggs upp steg för steg och alla händelser som skett finns lagrade, även
om inte bokningen genomförs fullt ut, blir det väldigt enkelt att ta fram en sådan
rapport.
En av de största fördelarna med CQRS, DDD och Event Sourcing är att utvecklare
kan fokusera på domänen och dess beteende, med andra ord vad affärsverksamheten
ska kunna hantera, samt hur det ska presenteras. Modellering av tabeller och relationer
mellan dessa , optimera och normalisera tabellerna existerar överhuvudtaget inte. Att
optimera för att få snabbare svarstider från databasen existerar inte heller, eftersom
data lagras som det ska presentera hämtas ”vyn” med dess Id, i utvecklartermer
”GetById(Id)”. Däremot kommer redundant data troligtvis att lagras, det vill säga
samma data i flera olika läsmodeller. Att spara ner alla händelser gör också att
datamängden som lagras blir mycket större. Däremot är datalagring idag relativt billigt
samt att utvecklingskostnaden för att ta fram en relationsdatabas försvinner.
Konceptet med DDD och aggregat och aggregatrötter är också något som förenklar
komplexa situationer, då entiteter som hör ihop och dess affärslogik ”samlas” på ett
ställe.
20
5.2 Nackdelar
Det är lätt att bygga till komplexitet där det inte tidigare fanns. Om det inte är viktigt
för affärsverksamheten att kunna spåra om en användare har korrigerat en felaktig
adress eller ändra på grund av flytt (i de flesta fall är det troligtvis inte särskilt viktigt),
skapar man komplexitet. Att gå igenom alla steg i CQRS, det vill säga skapa ett
kommando, låta en kommandohanterare ta hand om kommandet, anropa en metod på
aggregatroten som i sin tur skapar en händelse som ska publiceras i Event Store och
en händelsehanterare som ser till att vymodellerna i läsdatabasen uppdateras. Många
steg för att uppdatera en adress. Däremot finns det en fördel om det i ett senare skede
av affärsverksamheten blir viktigt att kunna spåra om en användare har ändrat adress,
då allt som skett finns lagrat.
Vilket leder till ett annat problem. Om det från början inte är viktigt för en
affärsverksamhet att veta varför en användare byter adress, så kommer utvecklarna
troligtvis att ha skapa ett och samma händelseobjekt för både användaren har flyttat
och korrigera felaktig adress. Om det i ett senare skede blir viktigt för
affärsverksamheten att skilja på dessa två händelser kommer man ändå inte ha haft det
lagrat från start.
Förutom att redundant data kommer att lagras, blir det kodmässigt en del upprepande.
Ett kommando och händelseobjekt som publiceras i samband med kommandot
innehåller oftast samma data, vilket gör att utvecklare bryter mot DRY, ”Don’t Repeat
Yourself”, alltså att inte upprepa kod. Även utvecklingstiden ökar en aning då det är
fler steg att implementera.
21
6. Diskussion och slutsats
Syftet med rapporten var att undersöka för- och nackdelar med CQRS och Event
Sourcing jämfört med traditionella CRUD - system men även hur det kan
implementeras, när det kan användas och på vilket sätt man kan spåra vad användarna
gjort i systemet. I detta avslutande kapitel kommer personliga slutsatser att presenteras
utifrån fakta, resultat och analys, samt förslag till vidare forskning.
2009 började Greg Young och sedermera Udi Dahan att hålla föredrag om mönstren
CQRS och Event Sourcing och arkitekturen som kan skapas med dessa. Senaste året
har det börjat få genomslag och på de flesta större utvecklarkonferenser har de dykt
upp föreläsningar om arkitekturen från andra personer.
Som både Dahan [8] [4], Fowler [3] och Abdullin [5] påpekar är inte CQRS och Event
Sourcing till för alla system. Det finns tre scenarior där arkitekturen är användbar. Om
en affärsverksamhet har behovet av att kunna spåra vad användaren gör och föra
historik över det med möjligheten att kunna gå tillbaka till tidigare tillstånd är CQRS
och Event Sourcing en väldigt bra lösning. Om endast systemet behöver hålla historik
över vad som hänt kan det räcka med att lagra loggningshistorik i en separat databas
utan att dra in CQRS och Event Sourcing.
CQRS och Event Sourcing kan vara användbart i de scenarion där
affärsverksamhetens krav och behov ändras efterhand, och där det är viktigt att
förändringar kan ske snabbt [5]. Istället för att behöva modellera om en databas, dess
tabeller och associerad kod vid förändrade krav från affärsverksamheten, är det enkelt
att med CQRS utöka med nya kommandon, händelser och spela upp händelserna igen
och uppdatera läsmodellerna.
CQRS kan förenkla utveckling vid komplexa modeller [3], där obskyra
sammanfogningar och beräkningar mellan tabeller existerar. Med CQRS och Event
Sourcing slipper utvecklare de bitarna. Sällan består ett helt system av komplexa
lösningar, och som Fowler beskriver [3], CQRS ska användas där det finns behov för
det, annars skapas endast komplexitet. Och med uppdelning av läs och skriv är det
22
möjligt att skala dessa oberoende av varandra passar CQRS exempelvis bra till system
med hög trafik för att snabbt hämta data från läsdatabasen.
Samtidigt kan många av fördelarna lösas med andra sätt. No-SQL databaser börjar få
stort genomslag och tanken med dessa är att lagra denormaliserat data, vilket gör att en
del komplexitet försvinner samtidigt som de är kända för att vara väldigt snabba [10].
Som nämnts tidigare finns det en del diskussionsgrupper på internet angående CQRS
och Event Sourcing, de på Google Groups 7 är mest aktiva, och att det råder så delade
meningar angående mönstret gör att det ibland blir svårt att förstå och veta vad som är
kan anses som korrekt. En annan sak som är något förvirrande är hur Dahan, en av de
främsta förespråkarna, först skriver ett omfattande inlägg om CQRS och dess fördelar
[4] för att sen i ett senare inlägg såga alla fördelar och att utvecklare i princip aldrig ska
använda CQRS [11]. Det finns en del kodexempel tillgängliga på internet, men Dahan
menar på i sitt inlägg att alla exempelapplikationer som finns tillgängliga är fel och att
utvecklare bör undvika att använda ramverken som finns utan att specificera varför.
Att avsluta inlägget med att man borde besöka en av hans workshops för att förstå hur
man gör det korrekt blir väldigt konstigt och känslan av att han enbart vill tjäna
pengar.
Framtagandet av prototypen och resultaten i denna rapport är byggda på ett ramverk
och förståelse för hur saker och ting implementeras har gjorts utifrån exempelkod,
som antagligen Dahan med sitt inlägg menar är icke korrekt. Samtidigt är det smidigt
att använda sig av ett ramverk och det fyller ju faktiskt sin funktion med att förenkla
utvecklandet, och resultatet blir faktiskt att teorierna kring CQRS och Event Sourcing
uppfylls. Med andra ord blir Dahans utspel en aning förvirrande, speciellt när han inte
påpekar konkret vad som är fel, men som med allt inom utveckling av system gäller
det att hitta ett sätt som passar en själv.
7 https://groups.google.com/forum/?fromgroups#!search/cqrs
23
Som med de flesta mönster, arkitekturer och ramverk finns det för- och nackdelar,
men CQRS och Event Sourcing öppnar faktiskt upp en hel del intressanta möjligheter.
Som nämnts tidigare finns det mycket teori om CQRS men det hade varit intressant se
mer exempel på hur det används i system i praktiken. CQRS har bara ett par år på
nacken och bara det senaste året har det fått mycket uppmärksamhet inom
utvecklarbranschen. Det vore intressant att inom några år ta del av en liknande
undersökning, och se vad som har hänt på vägen och vilket resultat och slutsatser som
dras då. Personligen tror jag att CQRS och Event Sourcing kommer användas än mer
framöver. Något som också är intressant är att Microsofts ”Pattern and practices
team” har ett” open source” - projekt under utveckling 8, vilket framöver kanske leder
till ett lyckat ramverk så som ASP.NET MVC.
8 http://cqrsjourney.github.com/
24
7. Källförteckning
[1] Martin Fowler. (2005, December) Martin Fowler. [Online].
http://www.martinfowler.com/eaaDev/EventSourcing.html , [Hämtad: 12
april, 2012]
[2] Gregory Young. (2010, February) CodeBetter.com. [Online].
http://codebetter.com/gregyoung/2010/02/16/cqrs-task-based-uis-event-
sourcing-agh/ , [Hämtad: 20 april, 2012]
[3] Martin Fowler. (2011, July) Martin Fowler. [Online].
http://martinfowler.com/bliki/CQRS.html , [Hämtad: 12 april, 2012]
[4] Udi Dahan. (2009, December) Clarified CQRS - Udi Dahan. [Online].
http://www.udidahan.com/2009/12/09/clarified-cqrs/ , [Hämtad: 12 april,
2012]
[5] Rinat Abdullin. (2010, September) Theory of CQRS Command Handlers:
Sagas, ARs and Event Subscriptions. [Online].
http://abdullin.com/journal/2010/9/26/theory-of-cqrs-command-
handlers-sagas-ars-and-event-subscrip.html , [Hämtad: 10 maj, 2012]
[6] Julien Letrouit. (2010, Juli) Transitioning to Event Sourcing, part 1: the
DDD “light” application. [Online]. http://julienletrouit.com/?p=22 ,
[Hämtad: 2 maj, 2012]
[7] Eric Evans, Domain Driven Design - Tackling Complexity in the Heart of Software,
1st ed.: Addison-Wesley Educational Publishers Inc, 2003.
[8] Udin Dahan. (2010, February) Skills Matter - Podcast from London.Net User
Group: Udi Dahan on CQRS. [Online].
http://skillsmatter.com/podcast/open-source-dot-net/udi-dahan-
command-query-responsibility-segregation/rl-311 , [Hämtad: 2 april, 2012]
[9] Jonathan Oliver. (2010, September) CQRS: Sagas with Event Sourcing (Part
I of II). [Online]. http://blog.jonathanoliver.com/2010/09/cqrs-sagas-with-
25
event-sourcing-part-i-of-ii/ , [Hämtad: 10 maj, 2012]
[10] Jak Charlton. (2010, Oktober) CQRS - The Cult of Shiny Things. [Online].
http://devlicio.us/blogs/casey/archive/2010/10/29/cqrs-the-cult-of-shiny-
things.aspx , [Hämtad: 20 maj, 2012]
[11] Udi Dahan. (2011, April) When to avoid CQRS. [Online].
http://www.udidahan.com/2011/04/22/when-to-avoid-cqrs/ , [Hämtad: 12
april, 2012]
26
8. Bilagor
8.1 Bilaga 1: <Exempel på aggregatrot med entiteter>
27
8.2 Bilaga 2: <Lagring av bokningsöversiktsdata i läsdatas>
351 95 Växjö / 391 82 Kalmar
Tel 0772-28 80 00
dfm@lnu.se
Lnu.se
Recommended