15.3 CQRS and Event Sourcing: Separating Models from Storing Facts
An order system needs both fast queries of the current state and the ability to explain how that state evolved over time. To address this, the team proposes splitting the read and write models while preserving the event history.
CQRS and Event Sourcing often appear together, but they solve different problems: CQRS separates the write model from the read model; Event Sourcing treats the sequence of domain events as the authoritative record of state. You can use either one, or neither.
CQRS: Read and Write Models Use Different Models
The write model enforces invariants and is typically structured around aggregates and commands. The read model, in contrast, pre-aggregates data for specific queries, organized by pages, reports, or API responses.
Command → Write Model → Database
│
└─ events / CDC → Projection → Read Store → QueryCQRS does not require two separate databases. The lightest implementation can use different object models or views within the same database. Storage separation is only necessary when there are distinct differences in load, permissions, deployment, or storage characteristics.
Once separated, it's essential to clearly define read latency. A successful command does not guarantee that the read projection has been updated. Clients may need to:
- Accept temporarily stale data and display a timestamp indicating when it was last updated;
- Retrieve key results directly from the write model;
- Use version tokens to wait for the projection to catch up;
- Provide a dedicated path for "read-your-own-writes" scenarios.
Event Sourcing: State Is Folded from Events
Traditional persistence stores the current state; Event Sourcing stores the domain facts that led to that state:
EntryRequested(v1)
SeatReserved(v2)
PaymentAuthorized(v3)
RegistrationConfirmed(v4)When loading an aggregate, events are applied in sequence:
Registration rehydrate(List<RegistrationEvent> events) {
Registration state = Registration.empty();
for (RegistrationEvent event : events) {
state = state.apply(event);
}
return state;
}Appended events must include an expected version to prevent two commands from simultaneously writing to the same old state:
append(streamId, expectedVersion=4, newEvents)If the current version is not 4, the write fails, and the caller re-reads the state to decide whether to retry or report a conflict.
Events Are Long-Term Contracts
Once written into history, events may be replayed years later. Event evolution strategies include:
- Upcasting older versions to the current in-memory representation when reading;
- New consumers that understand multiple schema versions simultaneously;
- Appending corrective events to fix facts without altering previously recorded history;
- Prohibiting dependencies on external object references that may disappear over time.
Event names should express actual business facts, such as PaymentAuthorized, rather than vague or ambiguous references like RegistrationUpdated.
Snapshots and Projection Reconstitution
Snapshots are used to reduce the load time of aggregating long event streams, but they are not authoritative facts. Snapshots should record the event version they cover, enabling discard and reconstitution from events in case of corruption.
Projection processors also require idempotency and checkpointing. When reconstituting a large projection, the following steps should be followed:
- Build from scratch in a new table or new index;
- Continuously catch up with newly arriving events;
- Validate counts and business invariants;
- Atomically switch read traffic;
- Maintain a reversible window.
Don't Mistake Audit Logs for Event Sourcing
Recording "who changed which column when" has audit value, but it doesn't necessarily contain the complete semantic information needed to reconstruct business state. Event Sourcing requires events to be the authoritative source of state, ordered and verifiable, with a manageable evolution path, and supported by projection and recovery tooling.
Judging Whether to Adopt Event Sourcing
Signals indicating that Event Sourcing is worth considering:
- The historical facts themselves hold core business value;
- There is a need to trace a specific point-in-time state or recompute a new projection;
- The domain naturally expresses itself through commands and events;
- The team has the capability to manage versions, replay events, maintain snapshots, and perform recovery procedures.
Signals indicating that Event Sourcing is not suitable:
- It's merely a standard CRUD operation;
- The team hopes to use it as a "magic solution" for auditing;
- Stable event semantics cannot be clearly defined;
- The team lacks the ability to test event replay or disaster recovery.
References
- Martin Fowler, CQRS
- Martin Fowler, Event Sourcing
- Microsoft, CQRS pattern