Mönstret Händelsekällor
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 (skapa, läsa, uppdatera och ta bort) är en typisk dataprocess att läsa data från lagret, göra vissa ändringar i den 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, vilket kan sänka prestanda och tillgänglighet och begränsa skalbarhet, på grund av den bearbetning 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 ytterligare granskningsmekanism som registrerar information om varje åtgärd i en separat logg, går historik 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. När programmet lägger till nya order, lägger till eller tar bort objekt i ordern och lägger till leveransinformation, kan de händelser som beskriver dessa ändringar hanteras och används för att uppdatera den materialiserade vyn.
Dessutom är det när som helst möjligt för program att läsa historiken för händelser, och använda den för att materialisera det aktuella tillståndet för en entitet genom att spela upp och förbrukar alla händelser relaterade till entiteten. Detta kan inträffa på begäran för att materialisera ett domänobjekt vid hantering av en begäran, eller med en schemalagd aktivitet så att tillståndet för entiteten kan sparas som en materialiserad vy som stöder presentationslagret.
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.

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. Detta, i kombination med frånvaron av konkurrens under bearbetning av transaktioner, kan avsevärt förbättra prestanda och skalbarhet för program, särskilt för presentationsnivån eller användargränssnittet.
Händelser är enkla objekt som beskriver en åtgärd som har inträffat, tillsammans med eventuella associerade data som krävs för att beskriva den åtgärd som representeras av händelsen. Händelser uppdaterar inte ett datalager direkt. De bara registreras för hantering vid rätt tidpunkt. Detta kan förenkla implementering och hantering.
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.
Lagringen av endast tillägg till händelser tillhandahåller en verifieringskedja som kan användas för att övervaka åtgärder som vidtagits för ett datalager, generera om det aktuella tillståndet som materialiserade vyer eller projektioner genom att spela upp händelser när som helst samt hjälpa till vid testning och felsökning av system. Kravet på att använda kompenserande händelser för att ångra ändringarna tillhandahåller dessutom en historik över ändringar som har återförts, vilket inte skulle vara fallet om modellen bara lagrade det aktuella tillståndet. Listan över händelser kan också användas för att analysera programprestanda och identifiera trender i användarbeteendet eller 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 att ett program lägger till händelser i händelselagret som ett resultat av att en begäran hanteras, att händelserna publiceras och att händelsernas konsumenter hanterar dem. Under denna tid kan nya händelser som beskriver ytterligare entitetsändringar ha inkommit till händelselagret.
Anteckning
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–underordnad ögonblicksbild.
Ä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 anger en minskning av lagret tas emot i datalagret samtidigt som en order för artikeln läggs, vilket resulterar i ett krav på att stämma av de två åtgärderna antingen genom att underrätta kunden eller skapa en restorder.
Händelsepublicering kan vara minst en gång,så konsumenter av händelserna måste vara idempotenta. 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. Om flera instanser av en konsument till exempel underhåller och aggregerar en entitets egenskap, till exempel det totala antalet beställningar, måste bara en lyckas öka aggregeringen när en beställning görs-händelse inträffar. Detta är inte en nyckelegenskap för händelseursprung, men det är den vanligaste implementeringen.
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. Till exempel kan ändringar i en kundentitet avbildas som en serie specifika händelsetyper, till exempel Flyttad hem,Stängtkonto eller Avaktiveras.
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 och kunna spela upp dem för att återställa ett system, återställa ändringar eller behålla historik- och granskningsloggen. Om till exempel en uppgift omfattar flera steg måste du kanske utföra åtgärder för att återställa uppdateringar och spela upp vissa steg igen för att återställa informationen till ett konsekvent tillstånd.
När användning av händelser är en naturlig funktion i driften av programmet och inte kräver mycket vad gäller ytterligare utveckling och implementering.
När du behöver frikoppla processen att mata in eller uppdatera data från de uppgifter som krävs för att tillämpa de här åtgärderna. Det kan vara för att förbättra användargränssnittets prestanda eller för att distribuera händelser till andra lyssnare som utför en åtgärd när händelserna inträffar. Du kan till exempel integrera ett lönesystem med en webbplats för att skicka in omkostnader, så att händelser som skapats av händelselagret som svar på datauppdateringar 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 tillsammans med CQRS, måste du anpassa en läsmodell eller de vyer som exponerar data.
När detta används tillsammans med CQRS, och slutlig konsekvens är acceptabelt medan en läsmodell uppdateras, eller prestandapåverkan från återställning av entiteter och data från en händelseström tillåts.
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 granskningsspårning, historik och funktioner för att återställa och spela upp åtgärder inte krävs.
System där det endast finns en mycket låg förekomst av motstridiga uppdateringar i underliggande data. Till exempel system som främst lägger till data i stället för att uppdatera dem.
Exempel
Ett konferenshanteringssystem behöver spåra antalet slutförda bokningar för en konferens så att det kan kontrollera om det fortfarande finns platser när en möjlig 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.

Sekvensen med åtgärder för bokning av två platser är följande:
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.
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 överväga är att använda ögonblicksbilder (så att du inte behöver fråga och spela upp hela listan över händelser för att hämta det aktuella tillståndet för aggregeringen) och upprätthålla en cachelagrad kopia av aggregeringen i minnet.
Kommandohanteraren anropar en metod som exponeras av domänmodellen för göra bokningarna.
Samlingen
SeatAvailabilityregistrerar 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.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 tillhandahålla mer möjlighet till skalbarhet, ger användning av ett händelselager också en komplett historik, eller verifieringskedja, för bokningar och avbokningar för konferensen. Händelser i händelselagret är det korrekta registret. Det finns inget behov av att bevara samlingar på något annat sätt, eftersom systemet enkelt kan spela upp händelser igen och återställa tillståndet till valfri punkt i tiden.
Du hittar mer information om det här exemplet i Introduktion till händelsekällor.
Nästa steg
Martin Fowlers blogg:
Närliggande information
Följande mönster och riktlinjer kan vara relevanta när du implementerar det här mönstret:
Uppdelning av kommando- och frågeåtgärder (CQRS) . 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. Det datalager som används i ett system baserat på händelsekällor är vanligtvis inte lämpligt för effektiva frågor. I stället är en vanlig metod att generera förifyllda vyer av data med jämna mellanrum, eller när data ändras. Visar hur detta kan göras.
Kompenserande transaktionsmönster. Befintliga data i en lagerplats för händelsekällor uppdateras inte, i stället läggs nya poster till som ändrar tillståndet för entiteter till de nya värdena. Om du vill ångra en ändring används kompenserande transaktioner, eftersom det inte går att bara ångra den tidigare ändringen. Beskriver hur du ångrar det arbete som har utförts av en tidigare åtgärd.
Introduktion till datakonsekvens. När händelsekällor används med en separat läsningslagringsplats eller materialiserade vyer, är läsdata inte omedelbart konsekventa. Istället blir de endast slutligen överensstämmande. Sammanfattar problem med att bibehålla konsekvens över distribuerade data.
Riktlinjer för datapartitionering. Data partitioneras ofta när händelsekällor används för att förbättra skalbarheten, minska konkurrensen och optimera prestanda. Beskriver hur du delar in data i åtskilda partitioner och de problem som kan uppstå.