Skip to content

Projecting

Replaying builds state on demand, one entity at a time. A projection is the complement: a read model that EventSourcingKit keeps up to date continuously, in the background, as events are stored. Projections are how you build queryable views – a catalog of all books, say – without replaying them on every request.

How Projecting Works

EventSourcingKit runs a background observer that watches the event store. Whenever a new event is stored, the observer feeds it to your projections, which update their read models through reducers. The observer remembers its position with a checkpoint, so it resumes exactly where it left off after a restart – all of this is set up for you.

Defining the Projection

A projection derives from Projection<TId> and holds the shape you want to query:

using EventSourcingKit.Projecting;

public record Book : Projection<Guid>
{
    public string Title { get; init; } = "";
    public string Author { get; init; } = "";
    public bool IsCheckedOut { get; init; }
}

Its reducers look just like the ones for state, but live in the EventSourcingKit.Projecting namespace. An update reducer additionally tells the projection which entity an event belongs to, through GetId:

using EventSourcingKit;
using EventSourcingKit.Projecting;

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

public class BookCheckedOutReducer
    : IUpdateReducer<Book, Guid, BookCheckedOut>
{
    public Guid GetId(Event<BookCheckedOut> @event) => @event.Data.BookId;

    public Book Apply(Book projection, Event<BookCheckedOut> @event) =>
        projection with { IsCheckedOut = true };
}

Registering the Projection

A projection needs a data store that persists its read model. To get started, use the built-in InMemoryProjectionRepository, registered alongside EventSourcingKit:

using EventSourcingKit;
using EventSourcingKit.Defaults;
using EventSourcingKit.Projecting;

builder.Services.AddEventSourcingKit(
        builder.Configuration,
        [typeof(BookRegistered).Assembly]
    )
    .AddProjection<
        Book,
        Guid,
        InMemoryProjectionRepository<Book, Guid>,
        IProjectionRepository<Book, Guid>
    >();

You can now inject IProjectionRepository<Book, Guid> anywhere in your application to query the projection through GetById, GetAll, and more.

Moving beyond in-memory

The in-memory store is perfect for getting started, but its data is lost on restart. For durable read models backed by PostgreSQL or MongoDB, see Persisting Projections.

For More Information