Event sourcing explained: rebuild state from events
Replay a cart's events, load a snapshot and see a stale read model catch up. Compare event sourcing with CQRS and ordinary event delivery.
Event sourcing stores the business events that determine an entity's state. Replaying that ordered history rebuilds the state. It is a storage choice, not a delivery guarantee: concurrent writes, read-model updates and external effects still need explicit rules.
Keep the record of each cart change instead of only its latest total. Reading the changes again can rebuild the total, but it must not place the order or send its confirmation again.
A cart badge can show an old quantity even after the write succeeded. To explain that situation, separate the stored changes from the view built from them. The illustrative cart below has one product; its history is already committed, while its read model has not caught up.
Store the changes that determine the state#
The stored sequence is ItemAdded(2), ItemAdded(1), then ItemRemoved(1). Start at zero and apply each event: the quantity becomes 2, then 3, then 2. A projection is the calculation that turns this history into a useful view. Here the view contains only a quantity.
Rebuild a cart from its recorded events
Illustrative example: all events below are already stored. Predict the cart quantity after the next event, then advance the read model.
- ItemAdded(2)
- ItemAdded(1)
- ItemRemoved(1)
- Committed history
- 3 events; 2 items
- Read model
- 0 events; 0 items
Full replay gives quantities 2, 3, then 2. A snapshot after event 2 holds 3 items; applying only event 3 gives the same final 2. Enable JavaScript to step through it.
The read model can lag without losing the stored events. Reset changes the read model, not the history. This in-memory model has one product and no database, network, concurrent writers or external effects. A real snapshot must match its stream and projection version.
Try clearing the cart, then reset the read model. Clearing adds another event; resetting the view does not delete the history. Replaying the three original events returns 2 items. Replaying the fourth returns 0. The event list therefore answers a different question from a single row containing only the latest quantity: it shows how that quantity changed.
The defining choice is that accepted business events determine the state, rather than merely accompany unrelated updates. Microsoft’s Event Sourcing pattern describes the event store as the authoritative record. Do not infer the same application contract from a database recovery log or a change-data-capture feed; inspect what those records mean, how long they remain available and what can actually be rebuilt.
Replay the same history in Python#
Save this complete example as replay.py and run python3 replay.py. It calculates state from a supplied history and checks the snapshot result. It is not a persistent event store, a concurrent command handler or a performance benchmark.
The output is full replay: 2; snapshot replay: 2; cleared cart: 0. The function neither mutates the supplied history nor contacts another system. That separation matters: rebuilding a view must not send every historical confirmation again. Fowler’s discussion of external updates during replay identifies this distinction explicitly.
Snapshots save work, not coordination#
A snapshot saves derived state at a known event position. In the example, position 2 contains 3 items. Starting there and applying only event 3 produces 2, exactly as a full replay does. Store enough identity and version information to know which stream and projection produced a snapshot. Loading an incompatible snapshot is not a valid shortcut; rebuild it when the projection contract changes.
A snapshot does not settle competing writes. Suppose two commands both read stream version 3. The store must atomically enforce the chosen concurrency rule: an append expecting version 3 can succeed once, creating version 4; another append still expecting 3 is rejected. That command must reload and reconsider its decision. A separate application-side version check followed by an unguarded insert leaves a race. Microsoft’s pattern describes this optimistic concurrency boundary.
An asynchronous view also needs its own progress record. In the lab, that record is the last applied event number. A durable implementation must coordinate progress with the view update or make repeats harmless. The related idempotency guide explains why a saved identifier and its effect must agree.
Event sourcing, CQRS and event delivery#
Event sourcing answers how state is recorded and reconstructed. CQRS, short for Command Query Responsibility Segregation, separates the models used to change data and read it. Fowler’s CQRS explanation treats that separation as the defining choice, not a requirement to store events. You can use separate read and write models with ordinary database updates.
Event delivery concerns how another component receives a report. Publishing OrderPlaced after updating an order row does not make the row event-sourced. Equally, replaying a stored cart does not require a broker. Read what events do and do not promise for transport and ordering boundaries. When a database change must leave durable work for a publisher, the transactional outbox walkthrough addresses that separate problem.
When an ordinary update is simpler#
Use the ability to reconstruct meaningful history as the reason to consider this design. A preference that only needs its current value may be easier to store as an ordinary row. An audit requirement alone does not establish that every read model must be rebuilt from events.
Before choosing event sourcing, work through a concrete change to an old event format, an incorrect historical event and a failed projection rebuild. Those are maintenance tasks the design creates, not benefits obtained by naming the pattern. Keep correction and data-retention requirements explicit. For the cart here, the practical test is small: can the same retained events rebuild the intended quantity without repeating an external action?
Questions people also ask
3 QUESTIONSDo I need Kafka for event sourcing?
No. You need a store that preserves the required event history and enforces your ordering and concurrency rules. A broker can distribute events, but using a topic does not by itself establish those application guarantees.
Does event sourcing make every read eventually consistent?
No. An asynchronously updated read model can lag behind committed events. A read that reconstructs state from the committed stream, or a view maintained in the same supported transaction, has a different consistency boundary. State which kind of read you mean.
Is an audit log the same as event sourcing?
Not necessarily. An audit log may accompany ordinary row updates without being sufficient to rebuild the application state. This guide uses event sourcing to mean that the stored business-event history determines the state.