CQRS and Event Sourcing
Why command/query separation and event sourcing are not the same thing — and when each one earns its complexity.
slug: 002-cqrs-event-sourcing number: 2 title: "CQRS and Event Sourcing" description: "Why command/query separation and event sourcing are not the same thing — and when each one earns its complexity." youtubeId: null publishedAt: null anchor: authors: "Greg Young (CQRS); Bertrand Meyer (CQS, 1988)" year: 2010 title: "CQRS" institution: "—" venue: "Greg Young writing & talks"
The pattern at a glance
Long-form article coming soon. The narration below is the spoken version of this episode — read it as a quick transcript while the written companion is in draft.
Transcript
A customer places an order at 2:00 PM. Their dashboard shows the order at 2:03 PM. Three-minute lag.
The write database committed the order at 2:00. The read database — a separate one — hadn't caught up.
This is a CQRS system in production. The lag is the price you pay. The question is whether you knew that was the deal you were signing.
There are two patterns that get conflated all the time: CQRS and event sourcing.
CQRS — Command Query Responsibility Segregation — means separating the model that handles writes from the model that handles reads. Different databases. Different shapes.
Event sourcing means storing state as a log of every change instead of a current snapshot. Every change is appended. The current state is computed from the log.
People say they're doing one and mean the other. Or they assume you can't have one without the other. Both wrong.
The name CQRS comes from Greg Young, around 2010. It builds on a 1988 principle from Bertrand Meyer called Command-Query Separation — keep methods that change state separate from methods that read it.
Event sourcing is older still. Banking ledgers have worked this way since the fifteenth century. You don't store an account balance. You store every deposit and every withdrawal. The balance is a computation.
CQRS by itself is just this: write requests go to one model. Read requests go to another.
The write model is optimized for consistency and validation. One row per order. Normalized. The read model is optimized for queries. Maybe a denormalized search index. Maybe a graph database. Maybe twenty different projections — each tuned for one screen in the app.
The two stay in sync through some mechanism. A message bus. A change stream. A nightly batch job.
The cost: eventual consistency. The read model is always slightly behind the write model — it converges over time, never instantly. You give up read-your-own-writes guarantees in exchange for read scalability and freedom in modeling.
You can do this with two relational databases and no events. That's still CQRS.
Event sourcing is a different shape. State is a log of immutable events. OrderCreated. ItemAdded. PaymentReceived. PaymentRefunded. Every state change appends.
The current state is a projection — a function that walks every event in order and accumulates the result. You replay events from the beginning, or from the last snapshot, and arrive at the current state.
The win: a perfect audit trail. You can answer "what did this order look like on Tuesday?" because you have every change in order. You can build new read models by replaying the same events into a different shape.
The cost: schema evolution is painful. Once you've stored a million OrderCreated events with shape A, shape B is a migration. And replaying from event one is slow at scale, so you need snapshots.
You can do this with one database and no separation between reads and writes. That's still event sourcing.
The two together are powerful. The event log is the write model. Read models are projections rebuilt from the log.
Want a new query? Spin up a new projection. Replay the events. You have a new read database, never out of sync with the truth, because the truth is the log.
This combination is what most blog posts mean when they say "CQRS." Be precise about which one you actually mean.
Three hard parts.
One: event schema evolution. The day you persist your ten-millionth OrderCreated event, you'll want to change its shape. Now you need versioned event schemas, an upcaster — code that translates old events into the new shape — and a plan for the migration. Don't store events in your domain model's class shape. They will outlive that class by a decade.
Two: snapshots. Replaying every event from the beginning works for the first month. Then it doesn't. You need periodic snapshots — committed checkpoints of derived state — and an event-replay strategy that starts from the most recent snapshot.
Three: read-your-own-writes. The user submits a form. The next screen reads from the read model — which hasn't caught up. The user thinks the submit failed. The honest fix is to read from the write model for the user's own most-recent state, or to make the read model update synchronously on that request.
If your app is CRUD, you don't need either pattern. One database, one set of models, ship it.
If you don't need an audit trail or replayability, you don't need event sourcing. The log adds infrastructure cost and operational complexity for benefits you won't claim.
Both patterns are answers to specific problems. If you don't have the problem, don't take on the cost.
CQRS is about modeling. Event sourcing is about persistence. They solve different problems, and they pair beautifully when combined — but they are not the same thing.
If your team can name which one they're actually doing, they're already ahead of most.
Next episode: Raft. Distributed consensus, and the algorithm Diego Ongaro wrote because Paxos was too hard.