CQRS and Event Sourcing
CQRS: separate models/DBs for write (command) and read (query); Event Sourcing: instead of storing the current state, store every change as an event.
53.1 Why is it used?
- In e-commerce reads are 100x more than writes; scaling the read side separately (cache, search index) becomes easy.
- The write model is normalized and consistent, the read model is denormalized and fast: both have different needs.
- Event Sourcing gives you the full history: an audit trail (banking, wallet, order lifecycle) by design.
- You can rebuild the state at any past time (replay): debugging, and rebuilding read models after a bug fix.
- Other services (notification, analytics, search) can react to events in a loosely coupled way.
53.2 How it works
- Command: the intent to change state (PlaceOrder, CancelOrder); it is validated and applied on the write model.
- Query: only reads data; the read model (projection) is pre-built to suit the query, like a 'my orders' list.
- Event Store: an append-only log of events (OrderPlaced, PaymentDone, OrderShipped); events are never updated or deleted.
- Rebuild state: replay all events of an aggregate to get the current state; if there are many events, take a snapshot every N events.
- Projections: consume events and update the read DB (Elasticsearch, Redis, a SQL view); this gives eventual consistency.
- CDC (Change Data Capture): Debezium reads changes from the DB transaction log (binlog/WAL) and sends events to Kafka, without changing app code.
- Outbox Pattern: write the DB update and the event to an outbox table in one transaction, then a relay/CDC publishes to Kafka; this solves the dual write problem.
- Versioning: if the event schema changes, keep upcasting/a version field, because old events will always remain.
- 1.App: place order -> 1 PlaceOrder command -> Order Service (validate command, aggregate)
- 2.2 append -> Event Store (EventStoreDB / Axon): v1 OrderPlaced, v2 RestaurantAccepted, v3 RiderAssigned, v4 PickedUp, v5 Delivered; append-only, snapshot every N events
- 3.Outbox + Debezium CDC (WAL/binlog -> events) -> 3 publish -> Kafka topic: orders
- 4.4 consume events -> Projection service -> 5 update -> Elasticsearch / Redis read model: 'my orders', live status
- 5.6 query: the app's order tracking screen reads only from the read model; zero load on the write DB
Eventual consistency: the list may lag by 1-2 sec, handle it in the UI.
- 1.Refund dispute: v1 Placed -> v2 Accepted -> v3 Rider -> v4 PickedUp -> v5 Delivered; the support team reads all events and gives the exact timeline (audit trail by design)
- 2.Bug in the read model? Event Store (v1 ... v5) -> Fixed projection -> New read model; replay from the start, no data loss
CQRS can also be used without Event Sourcing.
53.3 Tools
| Tool | Type | How it is used |
|---|---|---|
| Axon Framework | Java CQRS/ES framework | A ready framework for commands, events, aggregates, sagas and projections; with Axon Server as the event store. |
| EventStoreDB (KurrentDB) | Event store database | Purpose-built append-only streams per aggregate, with subscriptions and projections built in. |
| Apache Kafka | Event streaming | Publish events to a durable log; consumers update read models; supports compacted topics and replay. |
| Debezium | CDC tool | Reads the MySQL binlog / Postgres WAL and sends every row change as an event to a Kafka topic; supports the outbox pattern. |
| DynamoDB Streams | AWS CDC | An ordered stream of every insert/update/delete on a table; a Lambda trigger updates the read model or search index. |
| Elasticsearch / Redis | Read model store | Store projections in denormalized form for fast search and low-latency reads. |
53.4 Comparison: EventStoreDB vs Kafka vs Debezium CDC vs DynamoDB Streams
| Point | EventStoreDB | Kafka | Debezium CDC | DynamoDB Streams |
|---|---|---|---|---|
| What it is | A dedicated event store DB | A distributed event log / streaming platform | A tool that extracts change events from the DB log | DynamoDB's managed change stream |
| Source of truth | Yes, the events are the truth | Can be, but mostly transport | No, the DB is the truth, events are derived | No, the table is the truth |
| Per-aggregate stream | Yes, lakhs of streams are easy | Ordering by partition key, not a topic per entity | Per table/row key | Per item key ordering |
| Retention / replay | Forever, replay any time | Configurable, with compaction | Depends on Kafka retention | Only 24 hours |
| Optimistic concurrency | Expected version check built in | No | No | No (use table conditional writes) |
| Ops effort | Medium (a separate DB) | High (cluster), lower with MSK/Confluent | Medium (Kafka Connect) | Very low: fully managed |
| Best for | Pure event sourcing (wallet, ledger) | Getting events to many consumers | Events from an existing DB without code changes | AWS serverless apps |
53.5 When to choose which
- CQRS only (without event sourcing): when reads are heavy and you need a separate read model (Elasticsearch/Redis); the most common case.
- EventStoreDB / Axon: when full audit history and state replay are a business requirement: wallet, ledger, insurance claims.
- Kafka: to deliver events to many services and replay them; the transport layer alongside an event store.
- Debezium CDC: when you have an old monolith DB, do not want to change code, but need events.
- DynamoDB Streams: to run a Lambda on table changes in an AWS serverless stack.
53.6 Real-world example
An illustrative scenario: a food delivery order lifecycle (Swiggy/Zomato-like).
- The user places an order: the PlaceOrder command goes to the Order service, is validated, and an OrderPlaced event is stored.
- More events get appended: RestaurantAccepted, RiderAssigned, PickedUp, Delivered; nothing is updated, only added.
- Through Outbox/CDC (Debezium) the events are published to the Kafka topic orders.
- A projection service consumes Kafka and updates the 'my orders' and live status read model in Elasticsearch/Redis.
- The app's order tracking screen reads only from the read model; there is no load on the write DB.
- A refund dispute comes in: the support team reads all events of that order to get the exact timeline.
- A bug is found in the read model: after the fix the events are replayed and the read model is rebuilt from scratch; no data loss.
53.7 Interview tips
- CQRS and Event Sourcing are separate concepts: say clearly that CQRS is also used without ES.
- Common mistake: using CQRS + Event Sourcing in every small CRUD app; complexity rises a lot, use it only where needed.
- Ignoring eventual consistency of the read model: the user placed an order and it does not show up in the list immediately; handle it in the UI.
- Separate dual writes to the DB and Kafka: if one fails the data mismatches; use Outbox or CDC. Trade-off: reads are ~100x writes, so scale the read side separately.