Deriving states from an event sequence
Event Sourcing: How Events Form a System's State
81 kilograms of material are available. Which receipts, reservations, and consumption events explain this state?
Event sourcing makes events the basis of state
Fowler and Microsoft describe event sourcing as an architectural decision about the authoritative basis of state: an additional change log alongside a primary state table is not sufficient.[1][2]
For a material batch, this raises a specific calculation question: which receipts, reservations, and consumption events explain the displayed available quantity? The following fictional example works through this calculation using a simplified inventory model.
Example: Inventory for a material batch
In the following simplified example, the projection starts at zero kilograms. A receipt increases inventory to 100 kilograms. Reservations reduce only the freely available quantity. Consumption for order A reduces both inventory and the reservation for that order. The following events are stored:
MaterialchargeEingebucht– 100 kgMaterialReserviert– 20 kg for order AMaterialVerbraucht– 18 kg in task T-17ReservierungFreigegeben– remaining 2 kg from order ABestandskorrekturErfasst– minus 1 kg following a confirmed discrepancy
This produces the following view after each event. Here, available means inventory minus reservations; no additional holds are included in the example.
| Event | Inventory (kg) | Reserved (kg) | Available (kg) |
|---|---|---|---|
| Receipt | 100 | 0 | 100 |
| Reservation for A | 100 | 20 | 80 |
| Consumption for A | 82 | 2 | 80 |
| Release remaining reservation | 82 | 0 | 82 |
| Inventory correction | 81 | 0 | 81 |
Another view may show only consumption by task. A third may collect all corrections for quality assurance. All views use the same domain events, but interpret them for different queries.
The example also illustrates a limitation. If MaterialVerbraucht contains no unit, affected material batch, or task reference, the current inventory might still be calculated somehow. Its operational meaning and subsequent traceability nevertheless remain inadequate. Event sourcing does not replace good event semantics.
Event and state
A state answers: what is the situation now? An event answers: what happened? In conventional state-based storage, a record might contain:
Status = freigegeben
An event-oriented model might instead contain the following sequence:
ChargeAngelegtPrüfungAngefordertNacharbeitGefordertNacharbeitAbgeschlossenChargeFreigegeben
The current state of “released” can be derived from this sequence. At the same time, it remains visible that the batch was sent back and reworked before release.
The domain meaning of individual process events is a separate question. An event needs clear semantics, a time reference, and context. Event sourcing, by contrast, describes the architectural principle under which such events form the system's state. Not every process event must be part of an event-sourced state model, and not every technical event automatically has operational significance.
Domain events rather than state changes
A good event names the meaning of a change. RestbestandAuf42Gesetzt describes only the result. ZweiEinheitenReserviert, by contrast, explains what happened and why the remaining quantity decreased. In this context, Microsoft distinguishes state-oriented from intent-oriented events: events with domain meaning enable more informative projections and subsequent analyses.[2]
This naming requires discipline. Events should describe facts in the past. An event is neither a wish nor a command. ChargeFreigeben would be a request; ChargeFreigegeben records that the substantive decision has already taken effect.
Generic technical labels such as StatusGeändert or DatensatzAktualisiert are usually too weak as well. They force later readers and systems to infer the meaning from old and new field values. Domain events instead make explicit which action, decision, or effect occurred.
The event stream as an ordered history
An event stream is the ordered sequence of events belonging to a particular domain entity. This may be an order, a batch, a piece of equipment, or another clearly defined object. Order is essential: ChargeFreigegeben before PrüfungAbgeschlossen would describe a different or invalid history compared with the reverse order.
Timestamps alone are not always sufficient to establish this order. Several operations may arrive almost simultaneously, systems may have clocks of different accuracy, and messages may be delayed. Events within a stream therefore often also receive a consecutive version or sequence number.[2]
This version number also supports control of concurrent changes. If two commands load the same starting state and both try to append a new event, the event store can detect that the stream has changed in the meantime. An operation is then rejected, reassessed, or retried in a controlled manner.[2]
The stream is not an arbitrary sequence of technical messages, however. Its boundaries must match the domain model. Forcing too many independent objects into one stream creates unnecessary conflicts. Distributing related changes too widely can undermine domain consistency.
From events to the current state
To derive the current state, the system begins with a defined initial state and applies the stream's events in order. For example, ChargeAngelegt, ChargeGesperrt, and ChargeFreigegeben produce a batch that is currently released.
This reconstruction becomes expensive when a stream is very long. Snapshots can therefore be used. A snapshot stores the derived state at a particular point. During rehydration, the system loads the latest snapshot and applies only the events that occurred afterward. The snapshot is a performance optimization; the event stream remains the authoritative sequence of events.[2]
Alongside the internal object state, projections or materialized views can be built. They translate events into data structures optimized for specific queries: an inventory overview, a list of pending reviews, or the current state of all batches. Such views can be rebuilt at any time, provided the events, processing logic, and required context remain available.
The projection must therefore not be confused with the source. A dashboard number shows the calculated current state. The event stream explains which changes produced that state.
Replay has consequences
The ability to replay events is one of event sourcing's most important properties. It enables states and projections to be rebuilt. Replay is safe, however, only if the same events produce the same internal result under controlled conditions.
External effects must not be silently triggered again during replay. A message already sent, a payment already executed, or a release already reported to another system should not happen a second time when a projection is rebuilt. Fowler explicitly highlights this problem: external systems do not automatically distinguish real processing from replay.[1]
External queries can also distort reconstruction. If the original calculation used an exchange rate, measurement, or external state, the same historical context must be available during later replay. Querying today's value instead would produce a different state.
Event handlers must also be able to handle duplicate delivery. Microsoft notes that consumers may receive the same event more than once. Processing should therefore be idempotent: repeated delivery of the same event must not affect the state more than once.[2]
A correction changes the calculation
In the inventory example, the last event reduces recorded inventory by one kilogram. It does not replace any earlier consumption. If the projection instead set inventory to an absolute value, that would be a different domain rule. The event type and payload must define this effect unambiguously.
In an append-only log, new entries are appended rather than earlier entries being overwritten. Event sourcing adds interpretation: a compensating event must also be processed by the state logic. The unchanged original action and its later correction jointly affect the result.[2]
Such a history also requires a planned retention and deletion strategy. Which data is stored directly in events and which is referenced in a controlled manner must be decided before capture; the architectural principle does not authorize unlimited retention.
Events evolve too
The operational domain and the software evolve. An event type may later need additional information, change its name, or be interpreted differently. Because historical events are retained over the long term, the system must be able to understand old and new schema versions together.
Possible strategies include version identifiers on events, default values for new optional fields, or so-called upcasters that translate older event formats into a current form when read. Microsoft also mentions direct migration of historical events, but treats it as a last resort because it breaks immutability.[2]
Changes to processing logic are more difficult still. An earlier operation may need to be understood under the rules applicable at the time, while new operations follow changed logic. Retaining event data alone is therefore insufficient. Reproducible historical states also require the relevant rules, versions, and external reference values to be taken into account.
These relationships concern data integrity. Complete event sequences lose their value if their semantics, order, versions, or processing are not reliably preserved throughout the lifecycle.
Event sourcing and CQRS
Event sourcing is often combined with Command Query Responsibility Segregation, or CQRS. CQRS separates models for write operations from models for queries. Events may be generated on the write side and then update one or more read-friendly projections.[2][3]
The patterns are not identical, however. Event sourcing can be used without CQRS. CQRS can in turn be implemented with conventional state-based storage. Combining them makes sense when different write and read requirements actually justify the additional effort.
Projections may lag behind the event store. This is eventual consistency: a newly stored event is already authoritative, while a read view does not yet show the new state. User interfaces and downstream processes must deliberately handle this interval.[2]
Deriving state in the 420+ architecture
The ledger architecture connects domain events with the batch and task states derived from them. For this kind of state derivation, the rule version applied and required reference values form part of the event sequence's context.
What event sourcing does not automatically provide
- An event store is not automatically a regulatory audit trail. Its purpose, scope, presentation, and review requirements must be defined separately.
- Append-only does not mean tamper-proof. Permissions, technical integrity controls, backups, and independent verification remain separate measures.
- A stored event does not prove substantive accuracy. An incorrect input can be stored correctly and still be wrong.
- Event sourcing is not suitable for every application. It increases the demands on modeling, versioning, testing, operations, and data protection.
- A message broker is not automatically an event store. Distributing messages and reliably storing domain streams serve different purposes.[2]
- Event sourcing is not the same as event-driven architecture. Systems can exchange events without deriving their state from a persistent event sequence.
Rebuilding is the decisive test
The table ends with 81 kilograms of available material. Its explanatory value lies in the path to that result: reservation, consumption, and cancellation of a remaining reservation have different effects. A mere sequence of numbers would conceal these distinctions.
When evaluating an event-sourcing design, a specific view should therefore be rebuilt. Is the result still correct after a correction, with an older event version, and with duplicate event deliveries? Do external effects remain controlled during this process? These questions show whether the stored sequence actually produces a reliably calculable state.
Primary sources and further reading
- Martin Fowler, Event Sourcing, December 12, 2005. Foundational article
- Microsoft Azure Architecture Center, Event Sourcing pattern. Architecture documentation
- Microsoft Azure Architecture Center, CQRS pattern. Architecture documentation