Event Sourcing vs CQRS
A customer sees “payment pending” after checkout, retries, and is charged twice. The design question is not whether the system uses fashionable patterns; it is whether commands reject duplicates, history explains what happened, and the read view tells the truth while it catches up.
Event Sourcing records domain changes as events and derives aggregate state from them. CQRS separates command and query models; it does not require Event Sourcing, separate databases, or asynchronous projections. This guide compares their distinct jobs, follows one order command into a read model, and defines when simpler CRUD is the safer choice.
Place both patterns in the wider event-driven architecture map. For broker trade-offs behind asynchronous projections, compare Kafka, RabbitMQ, and SQS.
Event Sourcing
In CRUD, updating an order row replaces selected current values; history exists only if you model it separately. Event Sourcing instead appends facts such as OrderCreated, ItemAdded, and PaymentAuthorized, then folds an aggregate's stream to recover current decision state. Snapshots and projections may store derived state, but the retained events remain the authoritative record.
An event store needs per-stream ordering and expected-version checks so concurrent commands cannot silently overwrite each other. EventStoreDB and Marten provide event-sourcing semantics directly; Kafka can carry retained events, but aggregate streams, optimistic concurrency, replay boundaries, and retention still need deliberate design. Snapshot frequency should follow measured replay cost, not a fixed event count.
| Pattern | Use when | Trade-off |
|---|---|---|
| CRUD | Current state and read/write shapes are straightforward | Simple path; history and integration delivery need explicit additions |
| CQRS | Commands and queries need materially different models | Clear separation; two models and possible synchronization |
| Event Sourcing | Domain facts and historical reconstruction are requirements | Replayable history; schema, replay, and retention obligations |
Quick reference
- Best for: audit requirements, complex domain lifecycles, temporal queries, event-driven systems.
- Strengths: explicit domain history, historical reconstruction within retained data, and new projections from old events.
- Weaknesses: query complexity, eventual consistency, learning curve, snapshot management.
- Treat recorded events as immutable facts; corrections use new events, while privacy and retention rules may require redaction or cryptographic erasure strategies.
- Snapshot only when measured replay cost justifies another derived artifact; verify that a missing or corrupt snapshot falls back to event replay.
- Schema evolution needs versioned readers or upcasters so retained historical events remain interpretable.
Remember this
Two appends expecting the same version means exactly one succeeds and one returns 409 — the expected-version check is what makes concurrent writes to one aggregate safe, and a corrupt snapshot must still fall back to full event replay.
CQRS
CQRS separates the model that accepts commands from the model that answers queries. The command side expresses intent and enforces invariants; the query side returns a shape designed for the caller. Both sides can begin in one process and one database. Separate stores and asynchronous projections are deployment choices, not part of the definition.
When the query model is built asynchronously, it can lag after a successful command. The API should expose that state honestly—for example, return the accepted command ID and let the client poll until the projection reaches that version. For a small CRUD screen whose read and write shapes are already simple, CQRS creates vocabulary and synchronization work without removing a bottleneck.
Quick reference
- Best for: command invariants and query shapes that genuinely need different models.
- Strengths: optimized read and write paths, independent scaling, multiple read models.
- Weaknesses: two models to maintain; eventual consistency only when synchronization is asynchronous.
- Write side: authenticate, validate commands, enforce invariants, and return conflict or not-found outcomes explicitly.
- Read side: return a view or
null; if a required projection version is not ready, return pending/retry metadata or use a defined authoritative fallback. - Do not use CQRS for simple CRUD — the overhead is not justified.
Remember this
CQRS separates reads from writes — use it when query patterns differ significantly from write patterns.
Using Event Sourcing and CQRS Together
Together, the flow is concrete: PlaceOrder loads the order stream, rejects a duplicate command ID or stale expected version, appends OrderPlaced, and returns the new stream version. A projector consumes that event idempotently and updates OrderSummary. A query may return the summary, null for an unknown order, or a pending response when its projection version trails the command result.
Read models are rebuildable only if events are retained, schemas remain readable, side effects are separated from replay, and the rebuild has been tested. Email must not be resent merely because a projection replays. The TypeScript-like pseudocode below makes duplicate delivery and unsupported events visible instead of assuming a perfect consumer.
Quick reference
- Event store = write model. Projections = read models. Events connect them.
- Rebuild read models only from a known checkpoint with retained, readable events.
- Frameworks: EventStoreDB, Axon Framework (Java), Marten ( .NET), Eventuate.
- Start simple: CRUD → CQRS (if read/write diverge) → Event Sourcing (if audit needed).
- Projections can lag — design UI for eventual consistency (optimistic updates, polling).
- Test duplicate delivery, poison events, checkpoint loss, and rebuilds without replaying external side effects.
Remember this
Combine them when you need both audit history and optimized reads — the event log feeds multiple read projections.
When NOT to Use These Patterns
A standard CRUD model with an audit table or outbox is often enough. Adding Event Sourcing to a simple task list creates replay, schema-evolution, and operational obligations. Adding CQRS when commands and queries use nearly the same shape creates two code paths without a compensating design benefit.
Use a requirement flow instead of a scale threshold. Need historical reconstruction from domain facts? Evaluate Event Sourcing. Need materially different command invariants and query shapes? Evaluate CQRS. Need reliable integration events but current-state CRUD remains appropriate? Use an outbox. If the requirement can be tested with a simpler model, keep the simpler model.
Quick reference
- Skip Event Sourcing if: no audit requirement, simple state, team lacks DDD experience.
- Skip CQRS if reads and writes have similar shapes and one consistency model serves both.
- CRUD plus an audit table or outbox can preserve history and publish events without Event Sourcing.
- Migration path: start CRUD, extract CQRS for hot read paths, add events when audit is required.
- Complexity budget: every pattern must earn its place with a concrete requirement.
- Interview tip: mention these patterns, explain trade-offs, but default to simpler solutions.
Remember this
Default to CRUD — adopt Event Sourcing and CQRS only when specific requirements justify the complexity.
Key takeaway
Event Sourcing answers “what facts define state over time?” CQRS answers “should commands and queries use different models?” They can be combined, but neither implies the other. Default to CRUD, an audit table, or an outbox until replayable domain history or divergent models provide a concrete benefit.
Practice (25 min): Model PlaceOrder as CRUD plus outbox, CQRS without Event Sourcing, and both patterns together; write the expected duplicate-command, missing-order, and stale-projection result for each. Intentionally replay an event and retry the projector after a simulated failure. Recover with idempotent side effects and a durable checkpoint, then state the requirement that would make you switch designs. Pass when replay rebuilds the read model without sending a second email.
Related Articles
Explore this topic