Um momento
0x80Lesson 9 of 15

CQRS and event sourcing

Separate writes from reads with CQRS, store state as a log of events with event sourcing, and build read models with projections.

28 min 7-question quiz 2 code exercises
By the end of this lesson you can
  • Explain CQRS and when separate read models pay off
  • Rebuild state from an event log, and use snapshots
  • Build and replay projections into read models

CQRS - command query responsibility segregation - splits a service’s model in two:

  • the write model handles commands (PlaceOrder, CancelOrder) and enforces the business rules;
  • one or more read models are shaped purely for queries: a dashboard of orders per planet, a customer’s order history, a search index.

Read models are updated from the write side’s events, so they’re eventually consistent - and you can have as many as you like, each in the best store for its queries. CQRS earns its complexity when reads and writes look very different or scale very differently; for simple CRUD it’s overkill.

Event sourcing

Event sourcing goes further: instead of storing an order’s current state and overwriting it, you store every event that happened to it - OrderPlaced, OrderPaid, DishesCooked, OrderDelivered - in an append-only event store. The current state is a fold over the events.

  • You get a perfect audit trail and can answer “what did this order look like at 18:42?”.
  • New read models can be built later by replaying history.
  • Long histories are slow to replay, so you periodically save a snapshot of the state and replay only the events after it.
  • The costs: events are forever (versioning them matters - see the last lesson), and querying requires projections.
event_sourcing.py
1events = [
2    {"seq": 1, "type": "OrderPlaced", "order": 42, "total": 1450},
3    {"seq": 2, "type": "OrderPaid", "order": 42},
4    {"seq": 3, "type": "OrderPlaced", "order": 43, "total": 990},
5    {"seq": 4, "type": "DishesCooked", "order": 42},
6    {"seq": 5, "type": "OrderCancelled", "order": 43},
7    {"seq": 6, "type": "OrderDelivered", "order": 42},
8]
9STATUS = {"OrderPlaced": "placed", "OrderPaid": "paid", "DishesCooked": "cooked",
10          "OrderDelivered": "delivered", "OrderCancelled": "cancelled"}
11
12def state_at(seq):
13    orders = {}
14    for event in events:
15        if event["seq"] > seq:
16            break
17        order = orders.setdefault(event["order"], {})
18        order.update(status=STATUS[event["type"]], **({"total": event["total"]} if "total" in event else {}))
19    return orders
20
21print("now:     ", state_at(6))
22print("at seq 3:", state_at(3))
Output
now:      {42: {'status': 'delivered', 'total': 1450}, 43: {'status': 'cancelled', 'total': 990}}
at seq 3: {42: {'status': 'paid', 'total': 1450}, 43: {'status': 'placed', 'total': 990}}

Try it

Which pattern fits?

Decide where each pattern is a good fit - or where a plain CRUD service is simpler and better.

0 of 6 sortedScore 0/0
  • “A ledger of every coin added to and spent from customer wallets, with full audit”

  • “A live dashboard of deliveries per planet, refreshed constantly by thousands of viewers”

  • “Admins editing the list of supported planets, twice a year”

  • ““Show me exactly what this order looked like before the kitchen changed it””

  • “Full-text search over menu descriptions”

  • “A settings page where each user picks their favorite noodle”

Key takeaways

  • CQRS separates a write model (commands, rules) from read models (queries), connected by events.

  • Event sourcing stores every change as an event; current state is a fold over them.

  • Snapshots speed up rebuilding; replay builds new read models from history.

  • Both add complexity - use them where audit, time travel or very different read needs justify it.

Lesson quiz

7 questions · pass with 5 correct · up to 50 XP

Passing this quiz completes the lesson and keeps your streak going. Questions you miss come back in review sessions later.

Practice: simulate microservice patterns in Python

Build small Python simulations of the patterns - routers, sagas, outboxes, circuit breakers, traces - and run them against sample inputs. They run locally in your browser; no servers or containers needed.

Exercise 1

Rebuild orders from events

+25 XP

Each input line is an event seq order EVENT (EVENT is OrderPlaced, OrderPaid, DishesCooked, OrderDelivered or OrderCancelled). Rebuild each order’s status by replaying the events in seq order, enforcing the lifecycle:

  • placed → paid → cooked → delivered, and cancelling is allowed only before cooking;
  • an event that isn’t allowed in the current state is rejected (seq 7: OrderDelivered rejected for order 43 (status paid)) and doesn’t change anything.

Then print each order’s final status by order number: order 42: delivered.

  • Busy evening
main.py
Loading editor…

Python runs in a sandboxed browser worker with a 60 second time limit. Its runtime loads from the Pyodide CDN; your code stays in this browser.

Exercise 2

A projection with a checkpoint

+25 XP

A read model counts orders per planet and the revenue from delivered orders. The first input line is the projection’s saved state: checkpoint N followed by its counts as planet=count pairs and revenue=cents. The remaining lines are events seq type order planet cents.

Apply only events after the checkpoint (they may include old ones, which must be skipped): OrderPlaced adds 1 to its planet’s count; OrderDelivered adds its cents to revenue. Print skipped N already-applied events, then the counts by planet alphabetically (Earth: 3), revenue: 4130, and checkpoint: 12 (the last seq applied).

  • Resume after a restart
main.py
Loading editor…

Python runs in a sandboxed browser worker with a 60 second time limit. Its runtime loads from the Pyodide CDN; your code stays in this browser.

Questions about this lesson

Stuck? Ask. Figured something out? Share it. Explaining is one of the best ways to learn.

Loading posts…

Gostou da aula? 😆👍
Apoie nosso trabalho com uma doação: