Architecture pattern comparison
Event sourcing vs CQRS
These two patterns show up together so often in blog posts and conference talks that many engineers assume they're one thing. They're not: event sourcing decides how you persist state; CQRS decides how you split reading state from writing it. Understanding the difference stops you from adopting more complexity than your problem actually needs.
TL;DR
- Event sourcing stores every change as an immutable event, and derives current state by replaying the event log — instead of overwriting a row in place.
- CQRS splits your write model (commands that change state) from your read model (queries that fetch state), often using entirely different data stores optimised for each.
- Combined, they're powerful but not required together. Event sourcing gives you a perfect audit trail; CQRS gives you fast, purpose-built reads. Adopt each on its own merits.
Side-by-side comparison
| Aspect | Event sourcing | CQRS |
|---|---|---|
| What it governs | How state is persisted (events, not rows) | How reads and writes are routed and modelled |
| Source of truth | The append-only event log | Whatever the write model defines — can be event-sourced or a normal table |
| Current state | Derived by replaying/folding events | Served directly from a dedicated, pre-built read model |
| Query performance | Poor without a projection — must replay history | Fast by design — read models are shaped for the query |
| Audit trail | Free — every change is a permanent, ordered record | Not inherent — depends on whether the write side also logs events |
| Consistency model | Strongly consistent within one aggregate's event stream | Read model is typically eventually consistent with the write model |
| Can exist alone? | Yes — with a single combined read/write model | Yes — with a conventional CRUD write model, no event log |
| Common pairing | Event store (EventStoreDB, Kafka) + projections | Command handler (write DB) + materialized view / search index (read DB) |
| Rollback / time travel | Trivial — replay up to any point in the log | Not applicable on its own; depends on the write side |
Code side-by-side
Recording a bank account withdrawal and answering "what's the balance?":
Event sourcing (no CQRS)
// Write: append an event, nothing is overwritten
eventStore.append("account-42", {
type: "MoneyWithdrawn",
amount: 50,
at: "2026-07-05T10:00:00Z",
});
// Read: replay ALL events to derive balance
function getBalance(accountId) {
const events = eventStore.read(accountId);
return events.reduce((balance, e) => {
if (e.type === "MoneyDeposited") return balance + e.amount;
if (e.type === "MoneyWithdrawn") return balance - e.amount;
return balance;
}, 0);
}
// Fine for one account; slow across millions CQRS (with a projection)
// Command side: handles the write, emits an event
function withdraw(accountId, amount) {
validateSufficientFunds(accountId, amount);
eventBus.publish({
type: "MoneyWithdrawn", accountId, amount,
});
}
// Projection: keeps the read model up to date
onEvent("MoneyWithdrawn", (e) => {
readDb.query(
"UPDATE account_balances SET balance = balance - $1 WHERE id = $2",
[e.amount, e.accountId]
);
});
// Query side: fast, direct read — no replay
function getBalance(accountId) {
return readDb.query(
"SELECT balance FROM account_balances WHERE id = $1",
[accountId]
);
} When to use event sourcing
- You need a complete, tamper-evident audit trail. Financial ledgers, compliance-heavy domains, and insurance claims benefit from an immutable record of every change and why it happened.
- Debugging "how did we get here" incidents matters. Replaying the exact event sequence that led to a bad state is far easier than reconstructing it from row diffs or logs.
- You expect to add new views of the same history later. New analytics or reporting requirements can be answered by writing a new projection and replaying existing events — no data migration required.
- The domain is naturally event-shaped. Order processing, workflow engines, and inventory systems often map cleanly onto "things that happened" rather than "current row values."
When to use CQRS
- Read and write workloads have very different shapes or scale. A product catalogue with millions of reads per write benefits from a denormalised, cache-friendly read model decoupled from the write model.
- You need multiple, differently-shaped views of the same data. A search index, a reporting dashboard, and a mobile API can each get their own optimised read model fed from the same write side.
- Write-side business logic is complex and read-side needs are simple. Keep validation and invariants in the command handler; let the read model be a flat, denormalised projection with none of that complexity.
- You can tolerate eventual consistency on reads. If a user seeing their own change appear a few hundred milliseconds late is acceptable, CQRS's async projection lag is a non-issue.
English phrases engineers use
Event sourcing conversations
- "We can replay the event stream to see exactly what happened before the outage."
- "That's an append-only log — we never mutate past events."
- "Let's add a snapshot so we don't replay 2 million events on every read."
- "The aggregate is rebuilt by folding all its events in order."
- "This bug is a data migration nightmare in a normal DB, but trivial here — just replay with the fix."
CQRS conversations
- "The read model is lagging — check the projection's consumer offset."
- "We keep the command side thin and push all the query complexity into projections."
- "That's a read-your-writes problem — the UI queried before the projection caught up."
- "Let's rebuild the projection from scratch after the schema change."
- "Don't put business logic in the query side — it's just a materialized view."
Quick decision tree
- Need a permanent audit trail / regulatory history → Event sourcing
- Read and write loads have wildly different shapes or scale → CQRS
- Both needs apply (audit trail + fast, varied reads) → Event sourcing + CQRS together
- Simple CRUD app, one query shape, low traffic → Neither — plain database, plain model
- Need multiple different read views of the same data → CQRS with several projections
- Team is new to the pattern and timeline is tight → Start without CQRS; add it when a real read bottleneck appears
Frequently asked questions
Are event sourcing and CQRS the same pattern?
No, though they are frequently used together. Event sourcing is about how you store state: instead of saving the current row, you store every state-changing event and derive current state by replaying them. CQRS (Command Query Responsibility Segregation) is about how you serve reads and writes: you split the model that handles writes (commands) from the model that serves reads (queries), often backed by different data stores. You can do CQRS with a normal database and no event log, and you can do event sourcing with a single combined read/write model.
Why do people combine event sourcing with CQRS so often?
Event sourcing naturally produces a stream of events, but replaying the full stream to answer a query (e.g. "show me this customer's current balance") is slow. CQRS solves that by projecting the event stream into one or more purpose-built read models (materialized views) that are optimised for the exact queries the application needs — so reads never touch the event log directly.
What is a "projection" in this context?
A projection is a process that consumes events from the event store, in order, and updates a read-optimised representation — a SQL table, a search index, a cache entry. If you need a new query shape later, you write a new projection and replay the existing event history to build it, without touching the source of truth (the event log).
Does event sourcing mean I lose the ability to query current state easily?
By itself, yes — a pure event store only lets you append and read events by stream. That is exactly the gap CQRS read models fill: you get an always-up-to-date, query-friendly snapshot without ever mutating the event log. Systems that adopt event sourcing without a CQRS-style projection layer usually end up building one anyway.
What is event replay used for besides answering queries?
Replay lets you reconstruct state at any point in history, debug "how did we get into this state" incidents by walking the exact sequence of events, run new business logic retroactively against historical data, and rebuild a corrupted or outdated read model from scratch by re-running all events through the new projection code.
Is CQRS always over-engineering for a simple CRUD app?
For a small CRUD app with one model and simple reads, yes — CQRS adds real complexity (eventual consistency between write and read sides, more moving parts) for no benefit. It earns its keep when read and write workloads have very different shapes or scaling needs (e.g. 100x more reads than writes, or reads that need a completely different schema like a search index), or when the domain naturally produces an audit trail that event sourcing captures for free.
How does this relate to eventual consistency?
In CQRS with separate read/write stores, the read model is updated asynchronously after a command is processed, so there is a window (usually milliseconds) where a client reads the read model and does not yet see its own last write. This is the same eventual-consistency trade-off discussed on our eventual-vs-strong-consistency page — CQRS is one of the most common places engineers encounter it in application code, not just in databases.