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¶
- Projections and Projectors covers the full set of reducer types and how projecting works under the hood.
- Observing Events and Checkpoints explains the observer and how checkpoints make projecting resumable.