SkillPilotAI

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.
Food delivery order: from write side to read side (Swiggy/Zomato-like)
  1. 1.App: place order -> 1 PlaceOrder command -> Order Service (validate command, aggregate)
  2. 2.2 append -> Event Store (EventStoreDB / Axon): v1 OrderPlaced, v2 RestaurantAccepted, v3 RiderAssigned, v4 PickedUp, v5 Delivered; append-only, snapshot every N events
  3. 3.Outbox + Debezium CDC (WAL/binlog -> events) -> 3 publish -> Kafka topic: orders
  4. 4.4 consume events -> Projection service -> 5 update -> Elasticsearch / Redis read model: 'my orders', live status
  5. 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.

Audit trail and replay
  1. 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. 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

ToolTypeHow it is used
Axon FrameworkJava CQRS/ES frameworkA ready framework for commands, events, aggregates, sagas and projections; with Axon Server as the event store.
EventStoreDB (KurrentDB)Event store databasePurpose-built append-only streams per aggregate, with subscriptions and projections built in.
Apache KafkaEvent streamingPublish events to a durable log; consumers update read models; supports compacted topics and replay.
DebeziumCDC toolReads the MySQL binlog / Postgres WAL and sends every row change as an event to a Kafka topic; supports the outbox pattern.
DynamoDB StreamsAWS CDCAn ordered stream of every insert/update/delete on a table; a Lambda trigger updates the read model or search index.
Elasticsearch / RedisRead model storeStore projections in denormalized form for fast search and low-latency reads.

53.4 Comparison: EventStoreDB vs Kafka vs Debezium CDC vs DynamoDB Streams

PointEventStoreDBKafkaDebezium CDCDynamoDB Streams
What it isA dedicated event store DBA distributed event log / streaming platformA tool that extracts change events from the DB logDynamoDB's managed change stream
Source of truthYes, the events are the truthCan be, but mostly transportNo, the DB is the truth, events are derivedNo, the table is the truth
Per-aggregate streamYes, lakhs of streams are easyOrdering by partition key, not a topic per entityPer table/row keyPer item key ordering
Retention / replayForever, replay any timeConfigurable, with compactionDepends on Kafka retentionOnly 24 hours
Optimistic concurrencyExpected version check built inNoNoNo (use table conditional writes)
Ops effortMedium (a separate DB)High (cluster), lower with MSK/ConfluentMedium (Kafka Connect)Very low: fully managed
Best forPure event sourcing (wallet, ledger)Getting events to many consumersEvents from an existing DB without code changesAWS 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).

  1. The user places an order: the PlaceOrder command goes to the Order service, is validated, and an OrderPlaced event is stored.
  2. More events get appended: RestaurantAccepted, RiderAssigned, PickedUp, Delivered; nothing is updated, only added.
  3. Through Outbox/CDC (Debezium) the events are published to the Kafka topic orders.
  4. A projection service consumes Kafka and updates the 'my orders' and live status read model in Elasticsearch/Redis.
  5. The app's order tracking screen reads only from the read model; there is no load on the write DB.
  6. A refund dispute comes in: the support team reads all events of that order to get the exact timeline.
  7. 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.