CQRS and Event Sourcing Lab
A shop runs a one-minute sale. Checkouts write orders while dashboards, search and each customer's “My orders” page read them. Keep everything in one model, or split reads from writes and feed a read model from events. Then fix a bug in that read model and find out whether its history can be rebuilt.
Background: CQRS, Event Sourcing, Transactional Outbox.
Run these in order
Share one model
Checkouts, dashboards and search use the same database. Outside the sale it copes. When three times the usual orders arrive at 0:15, the reports leave too little room for writes: checkouts slow to the two-second timeout and about a fifth of them fail. Drag the report load to 0% to see that the reports are the problem.
Split reads from writes
CQRS: checkouts write to their own store, and a projector copies each change into a read model for the reports. No checkout fails now. But one projector applies 30 events a second, the sale makes 66, and the read model falls about 24 seconds behind. Most customers open “My orders” and their order is not there, long after the sale ends.
Scale the projector
Run three projector instances. They keep up with the sale and the read model stays half a second behind. Try two: the backlog still builds a little. Size the read side for the peak, and watch its lag as closely as you watch errors.
Read your own writes
Back to one projector, but “My orders” shows a customer's new order from the write side until the read model has it. Every customer sees their own order. The dashboards are still 24 seconds behind: this hides the lag where people notice it most, but it does not remove it.
Fix a bug in a read model
The revenue projection has always counted cancelled orders as sales. A fix is deployed at 0:20, so new cancellations are counted correctly. But the write side keeps only the current rows, and the events were thrown away once delivered. The old figures cannot be recomputed, and the report stays about $38,000 too high.
Rebuild from the events
Event sourcing: every change is kept as an event in an event store. When the fix is deployed, a new read model is built next to the old one by replaying all 9,000 old events, and it takes over once it has caught up. The report is right again about ten seconds later. Try it with one projector: the replay takes most of the minute.
Replay events from an old format
The oldest events use the first version of OrderPlaced, which called the amount “total”. The fixed projection only understands the current version, so the replay stops at the very first event and the report stays wrong. Now tick “Upcast version 1 events”: old events are converted as they are read and the replay works. Stored events are never edited, so every version ever written must stay readable.
This lab simplifies reality on purpose. Traffic is flat apart from the sale, every event costs the projector the same, and a rebuild replays one event store into one read model. Real systems partition the event stream so projectors scale, take snapshots so aggregates load quickly, and monitor read model lag as a service level of its own.