How do CQRS and event sourcing combine in a real event-driven system?
CQRS splits writes from reads into separate models, and event sourcing makes the write model's source of truth a log of events instead of current state — together they let you rebuild any view of the past and scale reads independently.
LEARN BY DOINGPut your intuition to work3 steps
How do CQRS and event sourcing combine in a real event-driven system?
Use the lesson’s examples and explanation to check your idea.
Personal reflection. Nothing is sent to an AI service.CQRS and event sourcing are often mentioned in the same breath, but they answer different questions, and understanding where the line between them sits is exactly what separates a memorized definition from real command of the pattern.
#CQRS: separate models for writing and reading
Command Query Responsibility Segregation says: stop using one model to both accept mutations and serve reads. A command (PlaceOrder, CancelOrder) expresses intent, is handled by a write model that enforces business rules, and typically returns nothing more than an acknowledgment or an id — never a fully hydrated view of the data. A query (GetOrderSummary, GetOrdersForCustomer) never mutates anything and is served by one or more read models — data shapes optimized purely for how a UI or another service actually consumes them, which can be denormalized, pre-joined, cached, or indexed however is fastest for that specific read.
This split is valuable on its own, with or without event sourcing: write and read workloads usually have very different scaling needs (few, complex, validation-heavy writes vs. many, simple, latency-sensitive reads), and forcing both through one normalized relational model is a common source of both slow queries and overly defensive write logic.
#Event sourcing: the log is the source of truth
Event sourcing changes what a write model persists. Instead of storing current state and overwriting it in place — an orders row whose status column gets updated from pending to shipped — you persist every state transition as an immutable event: OrderPlaced, OrderShipped, OrderCancelled, each with a timestamp and a payload. Current state is never stored directly; it's derived by loading an entity's events, in order, and folding them into an in-memory object. The event log is authoritative — if a read model or a cache ever disagrees with it, the log wins, and the read model can simply be deleted and rebuilt from the log.
#Why they combine so naturally
Event sourcing already produces exactly what CQRS needs on the read side: a durable, ordered stream of facts about everything that happened. Once every write emits an event, building a new read model is just writing a new subscriber that projects those same events into a shape convenient for one query — you get read-model diversity essentially for free, without ever touching the write path.
Walking the flow: a PlaceOrder command loads the order aggregate by replaying its prior events (or a snapshot plus the events since), which validates that the order can legally be placed and, if so, produces a new OrderPlaced event. That event is appended to the event store — typically with an optimistic concurrency check against the aggregate's expected version, so two concurrent commands against the same aggregate can't silently overwrite each other's effects. Once durably appended, the event is published; every projection subscribed to it updates its own store independently and at its own pace.
#What you gain
- A complete audit trail for free. Every state change is already recorded with a timestamp and full context — no separate audit-log table to keep in sync.
- Temporal queries. You can answer "what did this order look like last Tuesday at 3pm" by replaying events only up to that point.
- Retroactive read models. Realize six months in that you need a new report or search index? Write the projector and replay the entire event log into it — no backfill script, no data migration, because the events already contain everything that ever happened.
- Recoverable projections. If a projector has a bug, fix it and replay; the read model is disposable and rebuildable, unlike a table that's the only copy of the truth.
- Independent scaling. Write throughput (appending events) and read throughput (serving queries from purpose-built stores) scale on completely different axes.
#What it costs
- Eventual consistency on the read side. A client that just issued a command may query a read model that hasn't caught up yet. Systems handle this with a "your action is being processed" UI state, by having the command return a version/sequence number the client can poll for, or by routing that client's immediate follow-up read to the write side.
- Event schema evolution. Events live forever, so a field rename or a new required field needs an upcaster that transforms old event shapes into the current one at read time — you can't just migrate a column in place.
- Snapshotting becomes necessary. An aggregate with hundreds of thousands of events would be prohibitively slow to rebuild by full replay every time; production systems periodically persist a snapshot of current state plus the version it was taken at, and only replay events after that.
- No ad-hoc queries against the write side. You cannot run an arbitrary
WHEREclause against an event log the way you would a relational table; every query pattern needs its own projection, decided in advance. - More moving parts operationally. An event store, a bus, and N projection stores are more to run, monitor, and reason about than one database.