Getting a Single Instance
When you need one read model and you have its key, ask Chronicle for it by key. You always call the same operation; what differs is where the answer comes from.
Chronicle knows whether the read model is materialized — whether an observer writes its state to a sink. If it is, the kernel reads the stored instance and releases any PII before returning it. If it is not — a passive read model has no observer and therefore no sink — the kernel rebuilds the instance from the events that feed it. You do not choose between the two at the call site; the read model’s own definition decides.
How it works
Section titled “How it works”The client sends the read model type and key to the kernel. The kernel resolves the read model’s definition and takes one of two paths:
- Materialized read model — the kernel looks the instance up in the sink by key, releases compliance-protected values, and returns it. The cost is a single storage lookup regardless of how long the event history is. The instance is as current as the observer that wrote it, so it is eventually consistent.
- Passive read model — the kernel replays the relevant events through the projection or reducer that owns the read model and returns the result. The instance reflects every event appended up to that call, so it is strongly consistent. The cost grows with the length of the history.
The materialized path is also the only one that sees the whole picture for a projection that joins across event sources or resolves keys from other events. Replay walks one event source, so it cannot reconstruct state that was built from events belonging to a different key.
Read by key
Section titled “Read by key”The read model type must already be registered by the client, either as a model-bound projection, a declarative projection, or the read model produced by a reducer.
AccountInfo? account = await eventStore.ReadModels.GetInstanceById<AccountInfo>(accountId);
if (account is not null){ Console.WriteLine($"{account.Name}: {account.Balance:C}");}val account = store.readModels.getInstanceByKey(AccountInfo::class, accountId)
if (account != null) { println("${account.name}: ${account.balance}")}import io.cratis.chronicle.IEventStore;import io.cratis.chronicle.java.BlockingEventStore;import io.cratis.chronicle.readModels.ReadModel;
@ReadModelrecord AccountInfo(String name, double balance) { AccountInfo() { this("", 0.0); }}
class ReadModelLookup { void printAccount(IEventStore store, String accountId) { var account = new BlockingEventStore(store) .getReadModels() .getInstanceByKey(AccountInfo.class, accountId);
if (account != null) { System.out.println(account.name() + ": " + account.balance()); } }}alias MyApp.ReadModels.AccountInfo
{:ok, account} = Chronicle.ReadModels.get_instance_by_id(AccountInfo, account_id)
if account do IO.puts("#{account.name}: #{account.balance}")endconst account = await store.readModels.getInstanceById(AccountInfo, accountId);
console.log(`${account.name}: ${account.balance}`);Each client names the operation in its own idiom, but the inputs are the same: the read model type and the read model key.
Missing instances
Section titled “Missing instances”If no instance exists for that key, clients return their normal “not found” shape: null, nil, or an empty result depending on the language. Treat that as “no state exists yet,” not as a projection failure. For a materialized read model this covers both a key that was never created and one that has been removed — the observer is authoritative, so Chronicle does not fall back to a replay that would resurrect it.
If the read model type itself is unknown, that is a configuration error. Register the projection, reducer, or read model before querying it.
Choosing which you get
Section titled “Choosing which you get”Because the read model’s definition decides the path, choose when you design the read model rather than when you query it.
Leave the read model materialized — the default — when:
- The same data is read often, or read from lists, dashboards, and user interfaces.
- The event history for a key can grow long.
- A brief lag between an append and the read reflecting it is acceptable.
- The projection joins events across event sources.
Mark the read model passive when:
- A command needs the exact current state before making a decision.
- You append an event and immediately need to read the state it creates.
- The event history for each key stays short.
- The read model is queried rarely relative to how often its events are appended.
Related topics
Section titled “Related topics”- Getting a Collection of Instances - Read or replay every instance of a read model
- Getting Snapshots - Inspect how one instance evolved over time
- Watching Read Models - React to read model changes as they happen
- Materialized Read Models - Page through sink-stored read models
- Passive projections - Keep a read model out of the sink so it is computed on demand