Architectural Styles Deep Dive · 8 of 8

CQRS + Event Sourcing — Two Halves of One Powerful, Heavy Idea

CQRS splits the write path (commands that change state) from the read path (queries that return data). Event Sourcing stores the sequence of events that produced state, instead of the state itself. They're often paired and often confused with each other; they're independent ideas that compose well — and bring the highest complexity ceiling on this list. Reach for them when audit, replay, or read-model variety is a real product requirement, not before.

CQRSEvent SourcingAuditEventual ConsistencyDDD
← Back to Architecture
Quick Facts

Two Ideas, Often Combined

Basic Concepts

  • CQRS — Command Query Responsibility Segregation: the model used to write data and the model used to read it are different. Same database or different — the shapes differ.
  • Event Sourcing: persist a sequence of immutable events. Current state is computed by folding the events. The event log is the source of truth.
  • You can do CQRS without ES. Many systems do — separate read replicas, materialized views, denormalized search indexes are all CQRS-flavored.
  • You can do ES without CQRS, though it's awkward — building queries against an event store usually means projecting it into a read model anyway.
  • Together: commands produce events; events are appended to the store; projections build read models that queries hit. Reads and writes are fully decoupled.
CQRS

Separating Reads from Writes

Why Split?

Reads and writes have different needs. Writes care about invariants, transactions, validation. Reads care about query shape, joins, latency. Forcing one model to serve both leads to overloaded entities and slow joins — or denormalization that drifts out of sync with the rules.

CQRS lets each side optimize independently. Write side: a normalized OLTP DB tuned for consistency. Read side: a denormalized projection (Postgres views, Elasticsearch, Redis, a graph index) shaped for the queries that actually run.

Sync vs Async Projection

Sync (same transaction): writes update the read model atomically. No staleness, no extra infra, but the read model has to live in the same DB.

Async: writes emit events; a projector updates the read model after the fact. Read can lag by milliseconds to seconds. The classic CQRS shape.

Eventual Consistency on the Read Side

Async projection means "save then read" can return stale data. UIs need to plan for this — optimistic updates, "your change is processing" states, or a read-from-write fallback for the user's own recent writes.

Event Sourcing

Storing the Story, Not the Snapshot

The Core Idea

Don't UPDATE rows. Append events. OrderPlaced, ItemAdded, DiscountApplied, OrderShipped — every state change is recorded as a fact. To get the current state of an order, replay its events from the start.

What You Buy
  • Perfect audit log. Every change is preserved. "Why does this account have a $50 balance?" is answered by reading its events.
  • Time travel. Replay events up to any past timestamp to see what state was then.
  • New projections retroactively. Need a new read model? Replay history into it. No migration of historical data.
  • Rich domain model. Forces explicit business events with meaningful names — much more expressive than "row was updated."
  • Debugging. Reproduce a bug by replaying the exact event sequence that caused it.
What You Pay For
  • Schema evolution forever. Yesterday's events must still deserialize. Plan upcasters and versioning from day one.
  • GDPR / right to erasure. The event log is immutable by design — but personal data sometimes has to disappear. Solutions: crypto-shredding (delete the encryption key), tombstoning, or keeping PII in a separate referenced store.
  • Snapshotting. Replaying 100,000 events to load one aggregate is slow. Periodically snapshot state and replay only events since the snapshot.
  • Querying the event store directly is awful. You almost always need read projections — which is exactly why CQRS pairs with it.
  • Steep learning curve. Most engineers haven't seen it; mistakes are subtle and hard to undo.
Stores

Purpose-built: EventStoreDB, Axon Server, Marten (Postgres-based, .NET). Many teams roll their own on top of Postgres or Kafka — append-only table, monotonic version per aggregate, careful indexing.

Together

The Combined Picture

A typical CQRS+ES request flow:

  1. Client sends a command (PlaceOrder) to the write side.
  2. Write side loads the relevant aggregate by replaying its events (or from a snapshot).
  3. Aggregate validates the command against its invariants and produces new events.
  4. Events are appended to the event store atomically.
  5. A projector (or several) consumes the events and updates one or more read models.
  6. Queries hit the read model directly — they never touch the event store or the write aggregate.

Workflows that span aggregates use process managers / sagas reacting to events.

Decision

When (and When Not) to Pick This

SignalVerdict
Audit trail is a hard requirement (finance, healthcare, regulated industry)Event Sourcing is a strong fit.
Reads vastly outnumber writes, with multiple query shapesCQRS pays off.
Domain is naturally event-shaped (workflow, ledger, claims processing)Both are natural fits.
You'll need to build new read models retroactivelyEvent Sourcing wins.
CRUD app: users edit rowsDon't. Use a layered or modular monolith.
Team has never operated an event storeDefer. The learning cost is real.
You want CQRS only for query separationUse read replicas / materialized views first; full CQRS adds infra.

"Most applications most of the time should not use Event Sourcing." — Greg Young, who coined CQRS, on the appropriate use of his own pattern.

Continue

Other Architectural Styles