Overview
CQRS splits a system's write model (commands, invariants) from its read model (queries, projections); event sourcing stores state as the append-only sequence of domain events that produced it, rather than the current snapshot. They compose naturally — events feed the projections — but they are separate decisions with separate costs, and the classic mistake is adopting both wholesale when a modest read-model split (or neither) would do.
Key points
- CQRS in essence: commands go through the domain model that enforces invariants; queries hit purpose-built read models (denormalized tables, search indexes, caches) updated from the write side. It can be as light as "separate query services over the same database" — the full two-store version buys scalability and pays with eventual consistency between write and read.
- Event sourcing in essence: the event store is the source of truth; current state is a left-fold over events, with snapshots as an optimization. You gain a complete audit trail, temporal queries ("state as of March"), and retroactive new projections — capabilities a CRUD store simply cannot offer.
- The real costs: event schema evolution (versioning, upcasters — events are forever), eventual consistency surfacing in the UX (read-your-own-writes needs explicit handling), projection rebuild operations, and a steep unfamiliarity tax on the team. These dominate the decision more than any technical limit.
- Design anchors: events are facts, past tense, in domain language
(
OrderPlaced, notOrderUpdated); aggregates (DDD) define the consistency boundary a command sees; idempotent projections because events will be redelivered. - When it earns its keep: audit-critical domains (finance, healthcare), genuinely divergent read/write shapes or loads, temporal/behavioral analytics as a feature. When it doesn't: CRUD with symmetrical reads and writes — which is most software.
- Don't confuse with event-driven integration: publishing integration events between services is a messaging pattern; event sourcing is a persistence pattern. A system can do the former without the latter — most should.
- To explore: event stores (EventStoreDB, Postgres-as-event-store, Kafka's fit and misfit), Axon/eventuate frameworks, process managers vs. sagas atop event streams.
Practice
- Banking kata, event-sourced twist (source) — warm-up: store deposits and withdrawals as events and derive the printed statement as a projection — a left-fold in miniature.
- SimpleCQRS (m-r) (source) — Greg Young's reference implementation; read it, then port it to your language without peeking.
- Event-sourced bank account (exercise) — the full build from scratch: aggregate, event store with optimistic concurrency, idempotent projections, and the eventual-consistency UX handled honestly.
Related
- DDD — aggregates and domain events are the vocabulary both patterns build on.
- Asynchronous and distributed system patterns — the delivery guarantees projections depend on.
- Message brokers & event streaming — transport for events, and why a broker is not automatically an event store.
- Hexagonal architecture — the event store is just another driven port.