Marcelo Pastorino, Software Developer

Event Sourcing

Persisting application state as an append-only sequence of domain events instead of only the latest row values.

Event sourcing stores state as a sequence of domain events. The current state is derived by replaying those events, not by reading one mutable row and treating it as the only truth.

An order service does not only keep status = shipped. It keeps OrderPlaced, PaymentCaptured, ShipmentCreated, and rebuilds the current order from that history.

State vs history

Approach Source of truth Typical read path
CRUD state storage Current row or document SELECT the latest values
Event sourcing Append-only event log Replay events, or read a projection

Event sourcing makes what happened explicit. That helps auditing, debugging, and rebuilding read models after schema changes.

Event sourcing with projections

Core concepts

  • Event, an immutable fact in past tense, such as OrderPlaced.
  • Event store, append-only storage for events, usually partitioned by stream.
  • Stream, ordered events for one aggregate instance, such as order-123.
  • Aggregate, consistency boundary that decides which new events are valid.
  • Projection, a read model built by consuming events.
  • Snapshot, optional cached state after N events to speed replay.

Ordering matters inside a stream. Global ordering across the whole system is usually unnecessary and expensive.

How a command becomes state

  1. Load the aggregate’s event stream, or start empty.
  2. Apply business rules to the command.
  3. Append zero or more new events if the command is valid.
  4. Publish integration events or update projections asynchronously.
INSERT INTO order_events (stream_id, version, type, payload, occurred_at)
VALUES (
  'order-123',
  4,
  'OrderShipped',
  '{"orderId":"order-123","carrier":"ups","trackingId":"1Z999"}',
  now()
);

The version or sequence number prevents conflicting writes to the same stream. Two writers racing on one aggregate should fail safely instead of corrupting history.

Projections and replay

Most systems do not replay the full log on every query. Projectors consume events and maintain query-friendly views.

Replay is still a core capability:

  • Rebuild a read model after a bug in projector code.
  • Add a new projection without rewriting historical commands.
  • Recover analytics or audit views from the same source events.

Replay assumes events are immutable. Fixing mistakes means compensating with new events, not editing old ones.

Relationship to CQRS and EDA

Event sourcing and CQRS are often used together but are not the same thing.

  • CQRS separates command and query paths.
  • Event sourcing chooses events as the write-side source of truth.

A CRUD service can use CQRS without event sourcing. A service can use event sourcing with shared read and write models. In practice, event sourcing usually implies projections, which fits full CQRS well.

Downstream systems still benefit from event-driven architecture. Integration events can be published from projectors or from a relay after events are stored. If the write model is a normal database rather than an event store, the outbox pattern solves a different problem, keeping CRUD commits and broker publication aligned.

Snapshots and retention

Replaying thousands of events per aggregate gets slow. Snapshots store materialized state at a known version so replay starts from a checkpoint.

Retention is a product and compliance decision:

  • How long must raw events be kept?
  • Can sensitive fields be redacted in projections while events remain encrypted?
  • When can archived streams leave hot storage?

Plan storage growth up front. Event sourcing trades update simplicity for append volume.

When to use it

Event sourcing helps when:

  • Audit history and “why is it in this state?” matter legally or operationally.
  • Multiple read models must be rebuilt from the same facts.
  • Temporal queries matter, such as account balance at a past date.
  • The domain naturally fits domain events inside domain-driven design.

When to skip it

Avoid event sourcing when:

  • The team only needs current state and simple CRUD is enough.
  • Strong immediate consistency is required everywhere with no projection lag.
  • Operators cannot manage projector failures, replay jobs, and schema evolution.
  • The domain has no stable event vocabulary yet.

A transactional table plus outbox pattern is often simpler for publishing integration events from conventional state storage.

Trade-offs

  • Excellent auditability and rebuild options.
  • More moving parts than CRUD plus projections as an afterthought.
  • Read models are often eventually consistent with the write stream.
  • Event schema evolution must be deliberate. Additive changes are safer than redefining old event meaning.
  • Debugging improves for history, but projector bugs can produce wrong views until replayed.

Event sourcing is a persistence and modeling choice. Use it when the value of durable history and rebuildable projections outweighs the operational cost.

See also

  • CQRS, Separating commands that change state from queries that read it, with optional distinct write and read models.
  • Event-Driven Architecture, Decoupling producers and consumers through asynchronous messaging.
  • Outbox Pattern, Reliably publishing events by committing them in the same transaction as state changes.
  • Domain-Driven Design, Modeling software around the business domain, bounded language, and explicit context boundaries.
  • Eventual Consistency, A consistency model where replicas converge to the same state over time, allowing temporary divergence after a write.
  • Saga Pattern, Managing distributed transactions across services with a sequence of local transactions and compensating actions.

On Wikipedia