Mönstret Händelsekällor

Bookings

Istället för att bara lagra det aktuella tillståndet för data i en domän, används en lagringsplats för endast tillägg för att registrera alla åtgärder som vidtas på dessa data. Lagringsplatsen fungerar som postens system och kan användas till att materialisera domänobjekt. Detta kan förenkla uppgifter i komplexa domäner genom att datamodellen och affärsdomänen inte behöver synkroniseras, samtidigt som prestanda, skalbarhet och tillgänglighet förbättras. Det kan också tillhandahålla konsekvens för transaktionsdata, och samtidigt bibehålla fullständig granskningshistorik och historik som kan möjliggöra kompenserande åtgärder.

Kontext och problem

De flesta program arbetar med data, och det vanligaste sättet är att programmet behåller det aktuella tillståndet för data genom att uppdatera dem när användarna arbetar med dem. I den traditionella CRUD-modellen (create, read, update och delete) är en typisk dataprocess att läsa data från arkivet, göra vissa ändringar i det och uppdatera det aktuella tillståndet för data med de nya värdena , ofta med hjälp av transaktioner som låser data.

CRUD-metoden har vissa begränsningar:

  • CRUD-system utför uppdateringsåtgärder direkt mot ett datalager. Dessa åtgärder kan göra prestanda och svarstider långsammare och kan begränsa skalbarheten på grund av de bearbetningskostnader som krävs.

  • I en gemensam domän med många samtidiga användare är datakonflikter vid uppdatering mer sannolika, eftersom uppdateringsåtgärder sker i enskilda data.

  • Om det inte finns en annan granskningsmekanism som registrerar information om varje åtgärd i en separat logg går historiken förlorad.

Lösning

Mönstret Händelsekällor definierar en metod för att hantera åtgärder på data som styrs av en sekvens av händelser, där var och en registreras i en lagringsplats för endast tillägg. Programkoden skickar en serie händelser som uttryckligen beskriver varje åtgärd som har inträffat på data till lagringsplatsen för händelser, där de sparas. Varje händelse representerar en uppsättning ändringar av data (till exempel AddedItemToOrder).

Händelserna sparas på en lagringsplats för händelser som fungerar som arkivsystem (auktoritativ datakälla) för det aktuella tillståndet för data. Händelselagret publicerar vanligtvis dessa händelser så att konsumenterna kan få meddelande om dem och hantera dem om det behövs. Konsumenterna kan till exempel initiera uppgifter som använder åtgärderna i händelserna för andra system, eller utföra andra relaterade åtgärder som krävs för att slutföra åtgärden. Observera att den programkod som genererar händelser är frikopplad från de system som prenumererar på händelserna.

Vanliga användningsområden för de händelser som publicerats av lagringsplatsen för händelser är att upprätthålla materialiserade vyer för entiteter när åtgärder i programmet ändrar dem samt för integrering med externa system. Ett system kan till exempel underhålla en materialiserad vy över alla kundorder som används för att fylla i delar av användargränssnittet. Programmet lägger till nya beställningar, lägger till eller tar bort objekt i ordern och lägger till leveransinformation. De händelser som beskriver dessa ändringar kan hanteras och användas för att uppdatera den materialiserade vyn.

När som helst är det möjligt för program att läsa händelsehistoriken. Du kan sedan använda den för att materialisera den aktuella statusen för en entitet genom att spela upp och använda alla händelser som är relaterade till den entiteten. Den här processen kan ske på begäran för att materialisera ett domänobjekt när du hanterar en begäran. Eller så sker processen genom en schemalagd aktivitet så att tillståndet för entiteten kan lagras som en materialiserad vy för att stödja presentationsskiktet.

Bilden visar en översikt över mönstret, inklusive vissa av alternativen för att använda händelseströmmen, till exempel att skapa en materialiserad vy, integrera händelser med externa program och system samt att spela upp händelser för att skapa prognoser för det aktuella tillståndet för specifika enheter.

En översikt och exempel på mönstret Händelsekällor

Mönstret Händelsekällor ger följande fördelar:

  • Händelser är oföränderliga och kan lagras i en åtgärd för endast tillägg. Det användargränssnitt, det arbetsflöde eller den process som initierade en händelse kan fortsätta, och uppgifter som hanterar händelserna kan köras i bakgrunden. Den här processen, i kombination med att det inte finns någon konkurrens under bearbetningen av transaktioner, kan avsevärt förbättra prestanda och skalbarhet för program, särskilt för presentationsnivå eller användargränssnitt.

  • Händelser är enkla objekt som beskriver en åtgärd som har inträffat, tillsammans med alla associerade data som krävs för att beskriva åtgärden som representeras av händelsen. Händelser uppdaterar inte ett datalager direkt. De bara registreras för hantering vid rätt tidpunkt. Att använda händelser kan förenkla implementeringen och hanteringen.

  • Händelser har vanligtvis betydelse för en domänexpert, medan matchningsfel för objektrelationell impedans kan göra komplexa databastabeller svåra att förstå. Tabeller är artificiella konstruktioner som representerar det aktuella tillståndet för systemet, inte de händelser som inträffade.

  • Händelsekällor kan förhindra att samtidiga uppdateringar orsakar konflikter, eftersom de undviker kravet på att direkt uppdatera objekt i datalagret. Domänmodellen måste dock fortfarande utformas så att den skydda sig själv mot begäranden som kan resultera i ett inkonsekvent tillstånd.

  • Tilläggslagringen av händelser ger en spårningslogg som kan användas för att övervaka åtgärder som vidtas mot ett datalager. Det kan återskapa det aktuella tillståndet som materialiserade vyer eller projektioner genom att spela upp händelserna när som helst, och det kan hjälpa till att testa och felsöka systemet. Dessutom kan kravet på att använda kompenserande händelser för att avbryta ändringar ge en historik över ändringar som har ångrats. Den här funktionen skulle inte vara fallet om modellen lagrade det aktuella tillståndet. Listan över händelser kan också användas för att analysera programmets prestanda och identifiera trender för användarbeteende. Eller så kan den användas för att hämta annan användbar affärsinformation.

  • Händelselagret genererar händelser, och uppgifter utför åtgärder som svar på dessa händelser. Frikopplingen av aktiviteter från händelser ger flexibilitet och möjlighet att utöka. Uppgifter känner till händelsetyp och händelsedata, men inte den åtgärd som utlöste händelsen. Dessutom kan flera uppgifter hantera varje händelse. Detta möjliggör enkel integrering med andra tjänster och system som endast ska lyssna efter nya händelser som skapats av händelselagret. Dock brukar händelsekällans händelser vara på mycket låg nivå, och det kan vara nödvändigt att generera specifika integrationshändelser istället.

Händelsekällor används ofta tillsammans med CQRS-mönster genom att hanteringsaktiviteter utförs för data som svar på händelser, och genom materialisering av vyer från de lagrade händelserna.

Problem och överväganden

Tänk på följande när du bestämmer hur du ska implementera mönstret:

Systemet kan endast nå slutlig överensstämmelse genom att skapa materialiserade vyer eller genom att generera projektioner av data genom att spela upp händelser. Det finns en viss fördröjning mellan ett program som lägger till händelser i händelsearkivet som ett resultat av att hantera en begäran, de händelser som publiceras och konsumenterna av de händelser som hanterar dem. Under denna tid kan nya händelser som beskriver ytterligare entitetsändringar ha inkommit till händelselagret. Systemet bör utformas för att ta hänsyn till eventuell konsekvens i dessa scenarier.

Kommentar

Mer information om hur du hanterar slutlig datakonsekvens finns i Introduktion till datakonsekvens.

Händelselagret är den permanenta informationskällan, så händelsedata ska aldrig uppdateras. Det enda sättet att uppdatera en entitet om du vill ångra en ändring är att lägga till en kompenserande händelse i händelselagret. Om du måste ändra formatet (istället för data) för de beständiga händelserna, kan det kanske under en migrering vara svårt att kombinera befintliga händelser i lagringsplatsen med den nya versionen. Det kan vara nödvändigt att gå igenom alla händelser som gör ändringar så att de är kompatibla med det nya formatet, eller att lägga till nya händelser som använder det nya formatet. Fundera på om du ska använda ett versionsnummer på varje version av händelseschemat för att underhålla både det gamla och det nya händelseformatet.

Flertrådiga program och program med flera instanser kan lagra händelser i händelselagret. Konsekvenskontroll av händelser i arkivet händelse är viktigt, liksom ordningen för händelser som påverkar en viss enhet (den ordning i vilken ändringar i en entitet inträffar påverkar det aktuella tillståndet). Om du lägger till en tidsstämpel i alla händelser kan detta hjälpa till att undvika problem. En annan vanlig praxis är att kommentera varje händelse som är resultatet av en begäran med en inkrementell identifierare. Om två åtgärder försöker lägga till händelser för samma entitet samtidigt, kan händelselagret avvisa en händelse som matchar befintliga entitets-ID och händelse-ID.

Det finns ingen standardmetod, eller befintliga mekanismer som till exempel SQL-frågor, för att läsa händelserna för att få information. De enda data som kan extraheras är en dataström med händelser som använder händelse-ID som kriterier. Händelse-ID mappas vanligtvis till enskilda entiteter. Det aktuella tillståndet för en entitet kan endast bestämmas genom att spela upp alla händelser som är relaterade till entiteten mot det ursprungliga tillståndet för denna entitet.

Längden på varje händelseström påverkar hantering och uppdatering av systemet. Överväg att skapa ögonblicksbilder med ett visst intervall, till exempel ett angivet antal händelser, om strömmarna är stora. Det aktuella tillståndet för entiteten kan hämtas från ögonblicksbilden och genom att spela upp alla händelser som inträffade efter den tidpunkten. Mer information om hur du skapar ögonblicksbilder av data finns i Replikering av primär-underordnade ögonblicksbilder.

Även om händelsekällor minimerar risken för motstridiga uppdateringar av data, måste programmet fortfarande kunna hantera inkonsekvenser som uppstår från slutlig konsekvens och bristen på transaktioner. Till exempel kan en händelse som indikerar en minskning av lagerinventeringen komma in i datalagret medan en beställning för objektet görs. Den här situationen resulterar i ett krav på att stämma av de två åtgärderna, antingen genom att ge kunden råd eller genom att skapa en backorder.

Händelsepublicering kan vara minst en gång, och därför måste användarna av händelserna vara idempotent. De måste inte än en gång använda den uppdatering som beskrivs i en händelse om händelsen hanteras mer än en gång. Flera instanser av en konsument kan underhålla och aggregera en entitets egenskap, till exempel det totala antalet beställningar som görs. Endast en måste lyckas öka aggregeringen när en orderplacerad händelse inträffar. Även om det här resultatet inte är en viktig egenskap för händelsekällor är det det vanliga implementeringsbeslutet.

Den valda händelselagringen måste ha stöd för händelsebelastningen som genereras av ditt program.
Tänk på scenarier där bearbetningen av en händelse innebär att en eller flera nya händelser skapas eftersom detta kan orsaka en oändlig loop.

När du ska använda det här mönstret

Används det här mönstret i följande situationer:

  • När du vill fånga avsikt, syfte eller orsak i dina data. Ändringar i en kundentitet kan till exempel registreras som en serie specifika händelsetyper, till exempel Flyttat hem, Stängt konto eller Avliden.

  • När är det viktigt att minimera eller helt undvika förekomsten av motstridiga uppdateringar av data.

  • När du vill registrera händelser som inträffar kan du spela upp dem igen för att återställa tillståndet för ett system, återställa ändringar eller behålla en historik och granskningslogg. När en uppgift till exempel omfattar flera steg kan du behöva utföra åtgärder för att återställa uppdateringar och sedan spela upp några steg för att få tillbaka data i ett konsekvent tillstånd.

  • När du använder händelser. Det är en naturlig funktion i programmets drift och kräver lite extra utveckling eller implementering.

  • När du behöver frikoppla inmatningsprocessen eller uppdatera data från de uppgifter som krävs för att tillämpa dessa åtgärder. Den här ändringen kan vara att förbättra användargränssnittets prestanda eller distribuera händelser till andra lyssnare som vidtar åtgärder när händelserna inträffar. Du kan till exempel integrera ett lönesystem med en webbplats för inlämning av kostnader. Händelser som genereras av händelselagret som svar på datauppdateringar som görs på webbplatsen används av både webbplatsen och lönesystemet.

  • När du vill ha flexibilitet för att kunna ändra formatet för materialiserade modeller och entitetsdata om kraven ändras, eller – när de används med CQRS – måste du anpassa en läsmodell eller de vyer som exponerar data.

  • När den används med CQRS, och slutlig konsekvens är acceptabel när en läsmodell uppdateras, eller prestandapåverkan av att extrahera entiteter och data från en händelseström är acceptabel.

Det här mönstret kanske inte är användbart i följande situationer:

  • Små eller enkla domäner, system som har lite eller ingen affärslogik, samt icke-domänsystem som naturligt fungerar bra med traditionella CRUD-mekanismer för datahantering.

  • System där konsekvens och realtidsuppdateringar av datavyer krävs.

  • System där spårningsspår, historik och funktioner för att återställa och spela upp åtgärder krävs inte.

  • System där det bara finns en låg förekomst av motstridiga uppdateringar av underliggande data. Till exempel system som främst lägger till data i stället för att uppdatera dem.

Design av arbetsbelastning

En arkitekt bör utvärdera hur mönstret Händelsekällor kan användas i arbetsbelastningens design för att uppfylla de mål och principer som beskrivs i grundpelarna i Azure Well-Architected Framework. Till exempel:

Grundpelare Så här stöder det här mönstret pelarmål
Beslut om tillförlitlighetsdesign hjälper din arbetsbelastning att bli motståndskraftig mot fel och se till att den återställs till ett fullt fungerande tillstånd när ett fel inträffar. På grund av att samla in en historik över förändringar i komplexa affärsprocesser kan det underlätta tillståndsrekonstruktion om du behöver återställa tillståndslager.

- RE:06 Datapartitionering
- RE:09 Haveriberedskap
Prestandaeffektivitet hjälper din arbetsbelastning att effektivt uppfylla kraven genom optimeringar inom skalning, data och kod. Det här mönstret, som vanligtvis kombineras med CQRS, en lämplig domändesign och strategisk ögonblicksbild, kan förbättra arbetsbelastningens prestanda på grund av atomiska tilläggsåtgärder och undvikande av databaslåsning för skrivningar och läsningar.

- PE:08 Dataprestanda

Som med alla designbeslut bör du överväga eventuella kompromisser mot målen för de andra pelarna som kan införas med det här mönstret.

Exempel

Ett konferenshanteringssystem måste spåra antalet slutförda bokningar för en konferens. På så sätt kan den kontrollera om det fortfarande finns platser tillgängliga, när en potentiell deltagare försöker göra en bokning. Systemet kan lagra det totala antalet bokningar för en konferens på minst två sätt:

  • Systemet kan lagra information om det totala antalet bokningar som en separat entitet i en databas som innehåller information om bokningar. Vid bokning och avbokning kunde systemet öka eller minska antalet efter behov. Den här metoden är enkel i teorin, men kan orsaka problem med skalbarhet om ett stort antal deltagare försöker boka platser under en kort tidsperiod. Till exempel under den sista dagen innan bokningsperiodens slut.

  • Systemet kan lagra information om bokningar och avbokningar som händelser som lagras i ett händelselager. Det kan sedan beräkna antalet tillgängliga platser genom att spela upp dessa händelser. Den här metoden kan vara mer skalbar, eftersom händelserna inte förändras. Systemet behöver bara kunna läsa från händelselagret eller lägga till data i händelselagret. Händelseinformation om bokningar och avbokningar ändras aldrig.

Följande diagram illustrerar hur undersystemet för bokning i konferenshanteringssystemet kan implementeras med hjälp av händelsekällor.

Med händelsekällor som samlar in information om bokningar i ett konferenssystem

Sekvensen med åtgärder för bokning av två platser är följande:

  1. Användargränssnittet utfärdar ett kommando för att boka platser för två deltagare. Kommandot hanteras av en separat kommandohanterare. En logik som är fristående från användargränssnittet och som ansvarar för begäranden som skickas som kommandon.

  2. En samling som innehåller information om alla bokningar för konferensen skapas genom att fråga de händelser som beskriver bokningar och avbokningar. Den här samlingen kallas SeatAvailability, och ingår i en domänmodell som exponerar metoder för att fråga och ändra data i samlingen.

    Vissa optimeringar att tänka på använder ögonblicksbilder (så att du inte behöver köra frågor mot och spela upp den fullständiga listan över händelser för att hämta det aktuella tillståndet för aggregeringen) och underhålla en cachelagrad kopia av aggregeringen i minnet.

  3. Kommandohanteraren anropar en metod som exponeras av domänmodellen för göra bokningarna.

  4. Samlingen SeatAvailability registrerar en händelse som innehåller det antal platser som bokades. Nästa gång samlingen använder händelser, används alla bokningar för att beräkna hur många platser som finns kvar.

  5. Systemet lägger till den nya händelsen i listan över händelser i händelselagret.

Om en användare avbokar en plats, följer systemet en liknande process förutom att kommandohanteraren utfärdar ett kommando som genererar en avbokningshändelse och lägger till den i händelselagret.

Förutom att ge mer utrymme för skalbarhet ger användning av ett händelsearkiv också en fullständig historik, eller spårningslogg, av bokningar och avbokningar för en konferens. Händelser i händelselagret är det korrekta registret. Det finns ingen anledning att spara aggregeringar på något annat sätt eftersom systemet enkelt kan spela upp händelserna och återställa tillståndet till valfri tidpunkt.

Du hittar mer information om det här exemplet i Introduktion till händelsekällor.

Nästa steg

Följande mönster och riktlinjer kan vara relevanta när du implementerar det här mönstret:

  • CQRS-mönster (Command and Query Responsibility Segregation). Det skrivåtgärdslager som tillhandahåller den permanenta informationskällan för en CQRS-implementering baseras ofta på en implementering av mönstret Händelsekällor. Beskriver hur du åtskiljer åtgärder som läser data i ett program från åtgärder som uppdaterar data via separata gränssnitt.

  • Mönster för materialiserad vy. Datalagret som används i ett system som baseras på händelsekällor lämpar sig vanligtvis inte för effektiv frågekörning. I stället är en vanlig metod att generera förifyllda vyer av data med jämna mellanrum, eller när data ändras.

  • Kompenserande transaktionsmönster. Befintliga data i ett händelsekällalager uppdateras inte. I stället läggs nya poster till som övergår tillståndet för entiteter till de nya värdena. Om du vill ångra en ändring används kompenserande poster eftersom det inte går att ångra den tidigare ändringen. Beskriver hur du ångrar det arbete som har utförts av en tidigare åtgärd.