Skip to main content

Projections and Checkpointing

4. Asynchronous CQRS Projections & Checkpointing

A primary tenet of the CQRS pattern is the complete separation of your Write Model (optimized for committing atomic business facts) and Read Model (optimized for blazingly fast querying). Our Aggregate is the write model; it doesn’t support list queries or range filter aggregates efficiently. To solve this, we stream committed events into a flat, denormalized read model table using a Projection.

The Read Model Database Schema

For the counter UI, we need a flat table containing each counter’s latest computed value:

Implementing CounterProjection

The CounterProjection consumes the domain event envelopes and maintains this read model table:

Driving Projections with PersistedProjectionRunner

To keep this read model updated sequentially, we use PersistedProjectionRunner. When a command is executed, new events are appended. We load our last processed projection sequence from SpinSqliteCheckpointStore, fetch globally newer events from SpinSqliteEventStore, apply them sequentially to our projection, and update the checkpoint after each successful event. The projection write and checkpoint write are not one transaction, so projection updates must be idempotent: