Nozioni fondamentali su JournaledGrain
Le granularità journaled derivano da JournaledGrain<TGrainState,TEventBase>, con i parametri di tipo seguenti:
TGrainState
rappresenta lo stato della granularità. Deve essere una classe con un costruttore predefinito pubblico.TEventBase
è un supertipo comune per tutti gli eventi che possono essere generati per questo tipo di granularità e può essere qualsiasi classe o interfaccia.
Tutti gli oggetti stato ed evento devono essere serializzabili perché i provider di coerenza dei log potrebbero dover renderli persistenti e/o inviarli nei messaggi di notifica.
Per le granularità i cui eventi sono oggetti POCO (semplici oggetti C#), JournaledGrain<TGrainState> possono essere usati come abbreviazione di JournaledGrain<TGrainState,TEventBase>.
Lettura dello stato di granularità
Per leggere lo stato di granularità corrente e determinarne il numero di versione, JournaledGrain ha le proprietà
GrainState State { get; }
int Version { get; }
Il numero di versione è sempre uguale al numero totale di eventi confermati e lo stato è il risultato dell'applicazione di tutti gli eventi confermati allo stato iniziale. Lo stato iniziale, che ha la versione 0 (perché non sono stati applicati eventi), è determinato dal costruttore predefinito della classe GrainState.
Importante: l'applicazione non deve mai modificare direttamente l'oggetto restituito da State
. È destinato solo alla lettura. Invece, quando l'applicazione vuole modificare lo stato, deve farlo indirettamente generando eventi.
Generare eventi
La generazione di eventi viene eseguita chiamando la funzione RaiseEvent. Ad esempio, un granulare che rappresenta una chat può generare PostedEvent
per indicare che un utente ha inviato un post:
RaiseEvent(new PostedEvent()
{
Guid = guid,
User = user,
Text = text,
Timestamp = DateTime.UtcNow
});
Si noti che RaiseEvent
avvia un accesso di scrittura all'archiviazione, ma non attende il completamento della scrittura. Per molte applicazioni, è importante attendere fino a quando non viene confermato che l'evento è stato salvato in modo permanente. In tal caso, si segue sempre aspettando ConfirmEvents:
RaiseEvent(new DepositTransaction()
{
DepositAmount = amount,
Description = description
});
await ConfirmEvents();
Si noti che, anche se non si chiama ConfirmEvents
in modo esplicito, gli eventi verranno confermati: avviene automaticamente in background.
Metodi delle transizioni di stato
Il runtime aggiorna lo stato granulare automaticamente ogni volta che vengono generati degli eventi. Non è necessario che l'applicazione aggiorni in modo esplicito lo stato dopo la generazione di un evento. Tuttavia, l'applicazione deve comunque fornire il codice che specifica come aggiornare lo stato in risposta a un evento. Questa operazione può essere eseguita in due modi.
(a) La classe GrainState può implementare uno o più metodi Apply
in StateType
. In genere, si creerebbero più overload, e viene scelta la corrispondenza più vicina per il tipo di runtime dell'evento:
class GrainState
{
Apply(E1 @event)
{
// code that updates the state
}
Apply(E2 @event)
{
// code that updates the state
}
}
(b) La granularità può eseguire l'override della funzione TransitionState
:
protected override void TransitionState(
State state, EventType @event)
{
// code that updates the state
}
Si presuppone che i metodi di transizione non abbiano effetti collaterali diversi dalla modifica dell'oggetto stato e devono essere deterministici (in caso contrario, gli effetti sono imprevedibili). Se il codice di transizione genera un'eccezione, tale eccezione viene intercettata e inclusa in un avviso nel log Orleans, rilasciato dal provider di coerenza dei log.
Il momento esatto in cui il runtime chiama i metodi di transizione dipende dal provider di coerenza dei log scelto e dalla relativa configurazione. Le applicazioni non devono basarsi su un intervallo specifico, ad eccezione di quando garantito in modo specifico dal provider di coerenza dei log.
Alcuni provider, ad esempio il provider di coerenza dei log Orleans.EventSourcing.LogStorage, riproducono la sequenza di eventi ogni volta che viene caricata la granularità. Pertanto, purché gli oggetti evento possano essere deserializzati correttamente dall'archiviazione, è possibile modificare radicalmente la classe GrainState
e i metodi di transizione. Tuttavia, per altri provider, ad esempio il provider di coerenza dei log Orleans.EventSourcing.StateStorage, solo l'oggetto GrainState
viene salvato in modo permanente, pertanto gli sviluppatori devono assicurarsi che possano essere deserializzati correttamente durante la lettura dall'archiviazione.
Generare più eventi
È possibile effettuare più chiamate a RaiseEvent
prima di chiamare ConfirmEvents
:
RaiseEvent(e1);
RaiseEvent(e2);
await ConfirmEvents();
Tuttavia, ciò potrebbe causare due accessi di archiviazione successivi e comporta un rischio che la granularità abbia esito negativo dopo aver scritto solo il primo evento. Di conseguenza, in genere è preferibile generare più eventi contemporaneamente, usando
RaiseEvents(IEnumerable<EventType> events)
Ciò garantisce che la sequenza di eventi specificata venga scritta nell'archiviazione in modo atomico. Si noti che poiché il numero di versione corrisponde sempre alla lunghezza della sequenza di eventi, la generazione di più eventi aumenta il numero di versione di più di uno alla volta.
Recuperare la sequenza di eventi
Il metodo seguente dalla classe base JournaledGrain
consente all'applicazione di recuperare un segmento specificato della sequenza di tutti gli eventi confermati:
Task<IReadOnlyList<EventType>> RetrieveConfirmedEvents(
int fromVersion,
int toVersion);
Tuttavia, non è supportato da tutti i provider di coerenza dei log. Se non è supportato o se il segmento specificato della sequenza non è più disponibile, viene generata un'eccezione NotSupportedException.
Per recuperare tutti gli eventi fino alla versione confermata più recente, è necessario chiamare
await RetrieveConfirmedEvents(0, Version);
È possibile recuperare solo gli eventi confermati: viene generata un'eccezione se toVersion
è maggiore del valore corrente della proprietà Version
.
Poiché gli eventi confermati non cambiano mai, non ci sono gare di cui preoccuparsi, anche in presenza di più istanze o conferma ritardata. Tuttavia, in tali situazioni, il valore della proprietà Version
può essere maggiore al momento in cui await
riprende rispetto al momento in cui viene chiamato RetrieveConfirmedEvents
, pertanto può essere consigliabile salvare il valore in una variabile. Vedere anche la sezione relativa alle garanzie di concorrenza.