Skip to content

Replaying into State

A stream of events tells you everything that happened, but most of the time you want the current state of an entity: is this book checked out, and by whom? EventSourcingKit builds that state for you by replaying a subject's events through reducers.

Defining the State

State is a record that derives from State<TId>, where TId is the type of the entity's identifier. It holds only the fields you care about:

using EventSourcingKit.Replaying;

public record BookState : State<Guid>
{
    public string Title { get; init; } = "";
    public string Author { get; init; } = "";
    public bool IsCheckedOut { get; init; }
    public Guid? CheckedOutBy { get; init; }
}

Defining the Reducers

A reducer says how a single event changes the state, and you write one per event type. The first event uses an ICreateReducer, which builds the initial state; every subsequent event uses an IUpdateReducer, which evolves it:

using EventSourcingKit;
using EventSourcingKit.Replaying;

public class BookRegisteredReducer
    : ICreateReducer<BookState, Guid, BookRegistered>
{
    public BookState Create(Event<BookRegistered> @event) =>
        new()
        {
            Id = @event.Data.BookId,
            Title = @event.Data.Title,
            Author = @event.Data.Author
        };
}

public class BookCheckedOutReducer
    : IUpdateReducer<BookState, Guid, BookCheckedOut>
{
    public BookState Apply(BookState state, Event<BookCheckedOut> @event) =>
        state with { IsCheckedOut = true, CheckedOutBy = @event.Data.UserId };
}

Reducers are discovered automatically from the assemblies you registered, so there is nothing else to wire up. Each reducer receives a strongly typed Event<T>, giving you direct access to the event's Data.

Replaying to the Current State

With state and reducers in place, ask the event store to replay a subject:

var book = await eventStore.ReplayToCurrentState<BookState, Guid>(
    subject,
    cancellationToken
);

ReplayToCurrentState folds every event under the subject into a single, up-to-date BookState. This is the ideal way to load an entity before making a decision – for example, to confirm that a book is available before you store a BookCheckedOut event.

For More Information