Skip to content

Projections

A Chronicle projection is an observer that declaratively maps events onto a read model and keeps it up to date.

Your events are the source of truth, but events are a poor thing to read — nobody wants to replay a thousand MoneyDeposited facts to show an account balance. A projection does that folding for you: it watches a stream of events and continuously builds a read model — a shaped, queryable view that’s ready to serve to a UI.

The win is specialization. The same events can feed many projections, each tailored to one screen or question: a balance, a transaction list, a monthly summary. You never reuse one bloated model across conflicting needs — you build a focused read model per view, and each stays simple, fast, and independent.

maps & merges

Event

Projection

Event

Event

Read model

Query

Start with model-bound attributes, move to fluent IProjectionFor<T> when the mapping needs structure, and use a reducer when the logic reads better as state transitions; a self-reducing model is the default for new reducer code. Choose how to build a read model compares the forms on the same event history and explains when separation or dependency injection warrants an IReducerFor<T>, when to nominate a plain class, and when tooling-authored PDL fits.

Projections join events (never other read models). Fluent mappings use AutoMap by default; model-bound projections opt into event mappings with attributes such as [FromEvent<T>].

The one decision worth making up front is when the read model has to be correct:

Materialized (eventual)Passive (on demand)
When it updatesShortly after the event is appended (the default)Computed on demand, without a persistent sink
CostCheap reads, scales freelyEvery read folds the history from the start
Use it forAlmost everything — lists, dashboards, historyA value you must read back correctly right now, such as state a command decides on

Start materialized. Make a read model passive only when a workflow genuinely can’t tolerate it being a moment behind. When one caller occasionally needs its own write back, it can instead wait for the append’s observers. No projection updates synchronously inside the append, and a read cannot enforce uniqueness — use a constraint for that.

TopicDescription
ArchitectureHow the projection engine turns events into read models
Choose how to build a read modelOne decision path, worked examples, capabilities, and switching costs
Model-Bound ProjectionsBuild read models with attributes — the default style
Declarative ProjectionsBuild read models with the fluent IProjectionFor<T> API
Immediate ProjectionsWhy there is no synchronous projection mode, and what to use instead
Eventual ConsistencyThe default — how and when eventual projections catch up
Registration lifecycleWhat happens when projections register, change, fail, or are no longer declared
Tagging ProjectionsOrganize and tag projections
Appended event metadata filtersHow tags and metadata correlate reducers and reactors that run alongside projections

Once your read model exists, expose it to the frontend with a query — or see the whole loop in Build a full-stack feature.