Grunderna i JournaledGrain
Journalfördelade korn härleds från JournaledGrain<TGrainState,TEventBase>, med följande typparametrar:
TGrainState
Representerar kornets tillstånd. Det måste vara en klass med en offentlig standardkonstruktor.TEventBase
är en vanlig supertyp för alla händelser som kan aktiveras för det här kornet och kan vara vilken klass eller vilket gränssnitt som helst.
Alla tillstånds- och händelseobjekt ska vara serialiserbara (eftersom loggkonsekvensprovidrar kan behöva bevara dem och/eller skicka dem i meddelanden).
För korn vars händelser är POCO:er (vanliga gamla C#-objekt) JournaledGrain<TGrainState> kan användas som en förkortning för JournaledGrain<TGrainState,TEventBase>.
Läsa korntillståndet
Om du vill läsa det aktuella korntillståndet och fastställa dess versionsnummer har JournaledGrain egenskaper
GrainState State { get; }
int Version { get; }
Versionsnumret är alltid lika med det totala antalet bekräftade händelser, och tillståndet är resultatet av att tillämpa alla bekräftade händelser på det ursprungliga tillståndet. Det ursprungliga tillståndet, som har version 0 (eftersom inga händelser har tillämpats på det), bestäms av standardkonstruktorn för klassen GrainState.
Viktigt: Programmet bör aldrig direkt ändra objektet som returneras av State
. Det är endast avsett för läsning. När programmet vill ändra tillståndet måste det snarare göra det indirekt genom att skapa händelser.
Skapa händelser
Att höja händelser utförs genom att anropa RaiseEvent funktionen. Ett korn som representerar en chatt kan till exempel generera ett PostedEvent
för att indikera att en användare skickade ett inlägg:
RaiseEvent(new PostedEvent()
{
Guid = guid,
User = user,
Text = text,
Timestamp = DateTime.UtcNow
});
Observera att RaiseEvent
startar en skrivning till lagringsåtkomst, men väntar inte på att skrivningen ska slutföras. För många program är det viktigt att vänta tills vi har bekräftat att händelsen har sparats. I så fall följer vi alltid upp genom att vänta ConfirmEventspå :
RaiseEvent(new DepositTransaction()
{
DepositAmount = amount,
Description = description
});
await ConfirmEvents();
Observera att även om du inte uttryckligen anropar ConfirmEvents
kommer händelserna så småningom att bekräftas – det sker automatiskt i bakgrunden.
Tillståndsövergångsmetoder
Körningen uppdaterar korntillståndet automatiskt när händelser aktiveras. Programmet behöver inte uttryckligen uppdatera tillståndet efter att en händelse har skapats. Programmet måste dock fortfarande ange den kod som anger hur tillståndet ska uppdateras som svar på en händelse. Detta kan göras på två sätt.
(a) Klassen GrainState kan implementera en eller flera Apply
metoder på StateType
. Vanligtvis skapar man flera överlagringar och den närmaste matchningen väljs för körningstypen för händelsen:
class GrainState
{
Apply(E1 @event)
{
// code that updates the state
}
Apply(E2 @event)
{
// code that updates the state
}
}
(b) Kornet kan åsidosätta TransitionState
funktionen:
protected override void TransitionState(
State state, EventType @event)
{
// code that updates the state
}
Övergångsmetoderna antas inte ha några andra biverkningar än att ändra tillståndsobjektet och bör vara deterministiska (annars är effekterna oförutsägbara). Om övergångskoden utlöser ett undantag fångas undantaget upp och inkluderas i en varning i loggen Orleans som utfärdas av loggkonsekvensprovidern.
Exakt när körningen anropar övergångsmetoderna beror på den valda loggkonsekvensprovidern och dess konfiguration. Program bör inte förlita sig på en viss tidpunkt, förutom när det är specifikt garanterat av loggkonsekvensprovidern.
Vissa leverantörer, till exempel Orleans.EventSourcing.LogStorage loggkonsekvensprovidern, spelar upp händelsesekvensen varje gång kornet läses in. Så länge händelseobjekten fortfarande kan deserialiseras från lagringen är det därför möjligt att radikalt ändra GrainState
klassen och övergångsmetoderna. Men för andra leverantörer, till exempel Orleans.EventSourcing.StateStorage loggkonsekvensprovidern, sparas bara GrainState
objektet, så utvecklare måste se till att det kan deserialiseras korrekt när de läse från lagringen.
Skapa flera händelser
Du kan göra flera anrop till RaiseEvent
innan du anropar ConfirmEvents
:
RaiseEvent(e1);
RaiseEvent(e2);
await ConfirmEvents();
Detta kommer dock sannolikt att orsaka två på varandra följande lagringsåtkomster, och det medför en risk att kornet misslyckas när du bara har skrivit den första händelsen. Därför är det oftast bättre att skapa flera händelser samtidigt med hjälp av
RaiseEvents(IEnumerable<EventType> events)
Detta garanterar att den angivna sekvensen av händelser skrivs till lagring atomiskt. Observera att eftersom versionsnumret alltid matchar längden på händelsesekvensen ökar versionsnumret med fler än en i taget om flera händelser höjs.
Hämta händelsesekvensen
Med följande metod från basklassen JournaledGrain
kan programmet hämta ett angivet segment av sekvensen för alla bekräftade händelser:
Task<IReadOnlyList<EventType>> RetrieveConfirmedEvents(
int fromVersion,
int toVersion);
Det stöds dock inte av alla loggkonsekvensproviders. Om det inte stöds, eller om det angivna segmentet i sekvensen inte längre är tillgängligt, genereras en NotSupportedException .
Om du vill hämta alla händelser till den senaste bekräftade versionen anropar man
await RetrieveConfirmedEvents(0, Version);
Endast bekräftade händelser kan hämtas: ett undantag utlöses om toVersion
är större än det aktuella värdet för egenskapen Version
.
Eftersom bekräftade händelser aldrig ändras finns det inga raser att oroa sig för, inte ens i närvaro av flera instanser eller fördröjd bekräftelse. I sådana situationer kan värdet för egenskapen Version
dock vara större när await
återupptas än vid den tidpunkt RetrieveConfirmedEvents
då anropas, så det kan vara lämpligt att spara värdet i en variabel. Se även avsnittet om samtidighetsgarantier.