All event-sourcing patterns
Production pattern485 words · verified 10 September 2026

Aggregate Event Streams for Event Sourcing

An aggregate stream is the ordered history for one consistency boundary, such as an order, account, or workflow. Give every aggregate a stable entity ID, append only facts owned by that boundary, and derive current state by folding its events in version order.

Problem

Why this pattern exists

Streams become hard to reason about when they mirror database tables or UI screens instead of domain ownership. A single customer-wide stream creates needless write contention; one stream per field destroys the ability to enforce invariants. The useful boundary is the smallest unit that must accept or reject a command consistently.

Identity must survive renames and presentation changes. Prefer an opaque domain identifier such as order-01J... over an email address or display label. Put correlation IDs, causation IDs, actor identity, and source-system details in metadata so workflows can be traced without mixing transport concerns into business payloads.

Design decisions

Make boundaries explicit

  1. 01

    Choose a consistency boundary

    Group events that must be validated together. Separate histories that can progress independently, even when a read model later joins them. This keeps optimistic-concurrency conflicts meaningful instead of turning unrelated activity into one hot stream.

  2. 02

    Name facts, not commands

    Use past-tense event types such as order.placed and payment.captured. Events describe accepted facts. A rejected command belongs in an operational record only when rejection itself is a domain fact worth retaining.

  3. 03

    Keep stream order authoritative

    Treat aggregate version as the ordering signal. Wall-clock timestamps help operators and temporal queries, but clocks can collide or skew and should not replace stream version for state reconstruction.

AllSource implementation

Apply pattern to durable Core history

In AllSource Core, entity_id identifies the aggregate stream and each accepted event receives a version. Write structured facts through POST /api/v1/events, then query the entity history in ascending order. Query Service can fold that tenant-scoped history into current-state views without changing Core's durable record.

Start with one event type namespace per bounded context, for example order.*, shipment.*, and payment.*. Register payload schemas before producers multiply. When a process spans streams, keep correlation metadata across events and let a workflow projection show the joined story; do not collapse separate consistency boundaries into one stream for reporting convenience.

Order aggregate event sequence
order.placed        v1  { total: 14999, currency: "GBP" }
order.line_added    v2  { sku: "CHAIR-01", quantity: 2 }
order.confirmed     v3  { confirmed_by: "customer" }
order.dispatched    v4  { carrier: "DPD", tracking_ref: "..." }

Failure modes

Detect weak implementations early

One tenant or customer stream receives every write and conflicts constantly.

Fix: Split by real aggregate identity; retain tenant ID as scope, not aggregate boundary.

Events say update_order or set_status and cannot explain what happened.

Fix: Rename accepted outcomes as stable past-tense domain facts.

A renamed email or slug creates a second history for the same entity.

Fix: Use an immutable entity ID and keep mutable labels inside events or projections.

Production checklist

Ready when each statement is true

  • Aggregate owns one enforceable set of invariants.
  • Entity ID remains stable for lifetime of history.
  • Event types describe accepted facts in past tense.
  • Version, correlation, causation, actor, and tenant context are retained.
  • Cross-aggregate reporting happens in projections, not write streams.

Related patterns

Store history once

Rebuild every useful view from durable events.