The Kladde's sales pipeline has been event-sourced since module 4.4. The Deal aggregate decides in pure functions which events a command produces, and builds its state from events. The SqlEventStore appends the events to one stream per deal, with an expected version, and two read models follow the stream. What is still missing is the same thing the quotes were missing before lesson 4.5.2: two concurrent commands to the same deal run side by side, and only the event store notices the conflict.

Orleans has a base class of its own for event-sourced grains, JournaledGrain. In this lesson you put the deal into it, and in such a way that your event store stays the source and the read models from 4.4.5 notice nothing. Along the way you meet the hardest failure case of this module: a write that succeeds but comes back as an error.

JournaledGrain

JournaledGrain<TState, TEvent> from the package Microsoft.Orleans.EventSourcing is a grain base class with two type parameters:

  • TState is the state, a class with a public parameterless constructor. Orleans copies it internally, so it needs [GenerateSerializer].
  • TEvent is the common base type of all events of this grain.

The base class brings along:

Member Meaning
State the confirmed state: all stored events applied to the initial state; read-only
Version the number of confirmed events
RaiseEvents(events) queue events for writing, as one unit
ConfirmEvents() wait until all queued events are written
RaiseConditionalEvents(events) write events, but only if nobody else has written since the read; returns true or false
TransitionState(state, event) the method that applies an event to the state; you override it

How an event changes the state is something the grain says through TransitionState, and here the house rules apply as always: The grain calculates nothing itself, it asks the model.