State and Reducers¶
State is what you get when you fold a subject's events into a single value: the current title of a book, whether it is checked out, who holds it. EventSourcingKit builds state on demand by replaying events through reducers, and this is the model you use on the write side to make decisions before storing new events.
Defining State¶
A state type derives from State<TId>, where TId is the type of the entity's identifier:
public record BookState : State<Guid>
{
public string Title { get; init; } = "";
public bool IsCheckedOut { get; init; }
}
Every replayed state also exposes the Event it was last updated from, so you always know which event produced the current value. If a state spans more than one subject, derive from the non-generic State instead, which carries no identifier.
Writing Reducers¶
A reducer describes how one event type changes the state. The reducer interfaces live in the EventSourcingKit.Replaying namespace:
ICreateReducer<TState, TId, TEvent>builds the initial state from the first event, throughCreate.IUpdateReducer<TState, TId, TEvent>evolves an existing state, throughApply.IUpsertReducer<TState, TEvent>handles a state that is not keyed by a single subject, creating or updating it in one method.
public class BookRegisteredReducer
: ICreateReducer<BookState, Guid, BookRegistered>
{
public BookState Create(Event<BookRegistered> @event) =>
new() { Id = @event.Data.BookId, Title = @event.Data.Title };
}
Reducers are discovered automatically from the assemblies you register, the same way events are, so you never register them individually.
Replaying¶
Two methods turn events into state, each available for a subject or an EventQL query:
ReplayToCurrentState<TState, TId>returns the single, latest state. This is the common case.Replay<TState, TId>streams the state after every event, which is useful for auditing how a value evolved over time.
The natural use is to load an entity, check an invariant, and write: replay the state, confirm the action is allowed, then store the new event guarded by a precondition.
State versus Projections¶
State is built on demand and is ideal for write-side decisions. When you instead need a continuously maintained, queryable read model, use a projection, which applies the same reducer idea in the background.
For More Information¶
- Replaying into State is the introductory walkthrough.
- Storing Events and Preconditions shows how replayed state guards a write.
- Projections and Projectors is the read-side counterpart.