Glossary
Event Sourcing
The system does not merely overwrite its latest state. It stores what happened in the domain and in which order, then derives the present from that history.
Short definition
The event stream, not only the latest state row, is the source of truth.
Instead of updating an order directly from draft to shipped, an application appends immutable events such as OrderCreated, ItemAdded, PaymentConfirmed, and OrderShipped. Each describes a domain fact that has already happened. Applying these events in order recreates the current state of that order.
Event Sourcing changes both the write model and operational procedures. Historical events are long-lived contracts, projections can temporarily lag, and ordinary queries usually do not replay every stream on each request. It therefore fits cases where the value of history, domain intent, or rebuildable views outweighs this complexity.
The problem it solves
Current state alone does not explain how or why a system reached it.
In a process with meaningful history, the change can matter more than the final value. Event Sourcing preserves the order of decisions and supports several derived views.
- reconstructing the current state of an order or another aggregate
- finding the sequence of meaningful state changes without a separate audit mechanism
- building a new projection from stored history when read requirements change
- capturing intent explicitly, for example ItemAdded rather than an ambiguous QuantityChanged
- optimistic protection against concurrent appends to the same stream
- temporal analysis and domain scenarios for which current state alone is insufficient
Practical example
An order reconstructed from four events
The Order-8421 stream starts with OrderCreated. ItemAdded contributes a line item and price, PaymentConfirmed marks the order as paid, and OrderShipped records hand-off to the carrier. When the application must decide on another change, it loads these events in order and invokes their apply methods on a fresh object. The result is an order with the correct items, payment, and shipped status.
Reconstructing every aggregate for every HTTP list request would be wasteful. A projection handler therefore maintains an OrderList read model from the events. Its brief lag is a concrete example of eventual consistency: the authoritative history remains in the event store and the projection can catch up or be rebuilt. A new event is appended only when the expected stream version matches.
Text diagram and alternative
event stream: Order-8421
0 OrderCreated
1 ItemAdded
2 PaymentConfirmed
3 OrderShipped
│
├── replay ──→ Order (state: shipped)
└── projection ─→ OrderList read model
append: aggregate ID + expected stream version
How it works
Events are appended to a stream and reapplied in the same order.
Text alternative: a command loads the event stream by aggregate ID → events reconstruct the aggregate → the aggregate checks rules and produces new events → the event store appends them with an expected version → projection handlers update read models.
- Load the stream A repository requests events by aggregate ID, such as Order-8421. The stream provides an unambiguous order and a current version.
- Reconstruct state A fresh aggregate applies OrderCreated, ItemAdded, and subsequent events in turn. Applying during replay changes in-memory state but must not send emails or repeat payments.
- Decide and produce events A command invokes a domain operation. The aggregate checks invariants against the reconstructed state and, if it accepts the change, produces one or more new events.
- Optimistic concurrency The event store appends only at the expected version. If another process changed the stream after it was read, the append fails and the application must reload state and reconsider the decision.
- Project events Handlers turn events into read models for lists, reporting, or search. An asynchronous projection can temporarily lag without changing the authoritative stream.
Main components and principles
Correctness depends on stream identity, ordering, and events that remain readable.
An event store must safely load and extend the history of one aggregate. Distribution to external consumers is a separate responsibility.
Event and event stream
An event is an immutable record of a past fact and carries the data required for later use. A stream groups one aggregate’s events and establishes their order.
Aggregate ID and version
The aggregate ID selects the history of a particular order. A version or sequence number protects order and acts as the expected version for optimistic concurrency; a timestamp alone is usually insufficient.
An event store is not a message broker
An event store is the database of authoritative streams, offering per-aggregate reads and conditional appends. RabbitMQ or another broker distributes messages to recipients. Event Sourcing does not require every internal event to be published automatically to a broker.
Domain and integration events
A domain event records a fact inside a model and may contain details required to rebuild it. An integration event is a more public and stable message for another system. It can be derived from a domain change without being a one-to-one copy.
Projection and read model
A projection transforms streams into data suited to a particular query. Projections often form the read side of CQRS, but Event Sourcing and CQRS are neither synonyms nor requirements of one another.
Snapshot
A snapshot optimises loading a long stream: it stores state at a particular version so only later events need to be replayed. It does not replace the stream or become a new source of truth.
Benefits and limitations
Complete history enables new capabilities and becomes a long-term commitment.
Potential benefits
- current state can be rebuilt from an authoritative sequence
- history preserves domain intent and supports investigation of meaningful changes
- new read models can be built by replaying events without changing the original write path
- append with an expected version gives natural optimistic protection for one stream
- domain logic can be tested as given events – when command – then new events
Limitations and common mistakes
- historical events are difficult to correct, delete, and adapt to a new schema
- projections, checkpoints, deduplication, and recovery increase operational and test surface
- long streams can slow reconstruction and require measured use of snapshots
- sensitive or personal data in immutable events complicates retention and erasure
- poorly named events such as EntityUpdated reduce the history to a technical change log
- ordering and conflicts across several aggregates are not solved by one stream’s optimistic concurrency
Distinctions and suitability
Event Sourcing is neither an audit table nor a universal CRUD replacement.
An audit log is usually a secondary record of changes whose loss does not prevent current data from loading. In Event Sourcing, the stream is the authoritative write model and the state cannot be correctly reconstructed without it. Replay deliberately reapplies historical events to state or a projection; ordinary retry repeats a failed operation and must handle idempotency.
Event Sourcing suits domains with valuable history, complex state transitions, and a need for new temporal views. A routine catalogue, simple user profile, or reference data is often clearer as current state in a relational database. If a team does not need history as its source of truth and cannot operate projections and event evolution, costs are likely to exceed benefits.
Evolution and operations
Old events must remain intelligible to new code.
An event schema is a long-lived contract. Adding a field, renaming a type, or correcting meaning must be tested against the stored history as well as new events.
- design events around domain facts and store the data required for future replay
- version the event envelope or type and support tolerant reading of older versions
- prefer new versions or read-time upcasters to rewriting history when schemas change
- persist projection checkpoints and process repeated delivery idempotently
- measure stream length and introduce snapshots only for a demonstrated reconstruction problem
- separate projection replay from side effects so it does not resend payments or emails
- test optimistic concurrency, ordering, projection recovery, and real evolution of old events
Common questions
Event Sourcing in practice
Is Event Sourcing only a detailed audit log?
No. An audit log is usually secondary. In Event Sourcing, the event stream is the authoritative source from which state is reconstructed; auditability is one consequence.
Does Event Sourcing require CQRS?
Not by definition. Event-sourced writes are often combined with CQRS because projections serve ordinary queries, but separating commands and queries is a distinct decision.
Is an event store the same as Kafka or RabbitMQ?
No. An event store must persist and load the ordered history of a particular aggregate and enforce an expected version. A broker primarily distributes messages to subscribers.
Does a snapshot modify stored history?
No. A snapshot only speeds up loading state at a particular version. Events remain the source of truth and the snapshot can be rebuilt.
What happens when an event schema changes?
New code must still read old records, for example through tolerant deserialisation, version-specific handlers, or an upcaster. Rewriting historical events is a risky last resort.
How I approach application architecture
I make history the source of truth only where its value justifies the long-term operational commitment.
For stateful processes I assess the value of history, aggregate boundaries, concurrency, and projection recovery. Complexity should protect concrete rules rather than imitate a familiar pattern.