Skip to content

Read models

A Chronicle read model is current state folded out of events. Arc makes that state available to a command as an ordinary constructor or method parameter, resolved for the key the command already carries — no query, no repository, no manual lookup.

This section explains the mechanism. To use it, start with Use current state in a command for the recipe, or Read models in commands for the full reference on each position.

A read model can be injected into command-scoped code because it is resolvable by key — the event source id Arc resolved from the command — through Chronicle’s read model store. That resolvability comes from a Chronicle backing artifact, so a read model is registered when it has one of:

You declare the dependency identically in every case — which artifact materializes the state is an implementation detail Arc hides.

The [ReadModel] attribute does not make a type injectable into commands. [ReadModel] is an Arc concept used for queries, and it can be backed by stores other than Chronicle — Entity Framework Core, for example. Injection into command scope only makes sense for a type that is resolvable by key, and key resolution is owned by the backing provider. Chronicle registers the read models it can resolve; a read model backed by another provider is registered by that provider, not by Chronicle.

The practical consequence: adding [ReadModel] to a record with no projection or reducer behind it will not make it appear in a validator. Add the backing artifact.

Validator · Provide() · Handle()Chronicle read modelsCommand contextCommandValidator · Provide() · Handle()Chronicle read modelsCommand contextCommandalt[instance exists][never projected orremoved][Key] · EventSourceId · ICanProvideEventSourceIdGetInstanceById(type, eventSourceId)release with compliance subjectread model instancenull

Step by step:

  1. Identity strategy — Arc inspects the command to determine its key, using one of the strategies in Resolving EventSourceId.
  2. Command context lookup — the resolved identity is read from the current CommandContext.
  3. Guard — if no usable identity is available, resolution fails with UnableToResolveReadModelFromCommandContext.
  4. Store query — Chronicle’s read model store is queried by the resolved identity.
  5. Subject release — if the command context carries a compliance Subject and the instance exists, it is released with that subject, so [PII] properties decrypt under the same identity used for the events.
  6. Result — the instance is returned, or null when the projection instance does not exist.

Resolution happens exactly once per command. The same instance is handed to the validator, Provide(), and Handle().

A [Key] or event source id tells Arc which instance to resolve; it does not prove that instance exists. A projection may never have been created, may have been removed, or may be mid-rebuild. Whether that is a normal business condition or a fault is yours to declare — see nullable versus required.

Read models are registered as command-scoped services:

  • The state is fetched once per command and shared across that command’s validator, Provide(), and Handle().
  • The instance is tied to the identity resolved from the command context.
  • It is disposed when the command completes.

And they are read-only snapshots:

  • Immutable in practice — changes made to an injected instance are not persisted anywhere.
  • Eventually consistent — the state reflects events processed so far, not necessarily every event appended.
  • Current as of the command — the fetch happens when the command runs.

To change state, return events from Handle() or use an aggregate root. A read model is an input to a decision, never the place a decision is recorded.

Both give a command access to current state, and they answer different questions.

Reach forWhen
Read modelYou need projected, possibly denormalized state to validate against or compute from, and eventual consistency is acceptable
Aggregate rootYou need to emit events, enforce an invariant inside a consistency boundary, or work from source-of-truth stream state
BothValidate against projected state, then make the change through the aggregate

If correctness depends on source-of-truth state under concurrency, prefer aggregate or event-stream state over a read model — or enforce it with a Chronicle constraint, which is checked at append time.

TopicDescription
Read models in commandsInjecting into a validator, Provide(), and Handle(), and what nullability means.
Read models from other providersInjecting one backed by Entity Framework Core or MongoDB, and declaring the key without Chronicle.
When resolution failsEvery failure mode, what it means, and how to fix it.