Event Sourcing & CQRS · Event Sourcing Concepts

Aggregates and Consistency Boundaries

Where invariants are enforced and how commands become events.

An aggregate (DDD) is a consistency boundary: a cluster of objects that enforces invariants (e.g., an account can't be overdrawn) and is changed by one command at a time. Command handling: load the aggregate's stream, validate the command against current state, emit new events, and append them with optimistic concurrency (expected stream version). Keep aggregates small; consistency across aggregates is eventual (via policies/sagas).

Aggregate sizing

Design choice Small aggregates Large aggregates
Contention Low High (many writers to one stream)
Invariants Local only Broader but slower
Stream length Short Long (snapshots needed)
Cross-entity rules Eventual (policies/sagas) Immediate
async def withdraw(store, account_id: str, amount: int) -> None:
    events, version = await store.read_stream(f"account-{account_id}")
    account = rehydrate(events)
    if account.closed:
        raise DomainError("Account closed")
    if account.balance < amount:
        raise DomainError("Insufficient funds")            # invariant enforced in the aggregate
    await store.append(f"account-{account_id}",
                       [{"type": "MoneyWithdrawn", "amount": amount}],
                       expected_version=version)           # fails if someone appended meanwhile

Common pitfalls

  • Huge aggregates with heavy write contention.
  • Invariants spanning aggregates enforced synchronously.
  • Appending without expected-version checks (lost updates).

Key points

  • Aggregates = consistency boundaries
  • Command → validate → events
  • Optimistic concurrency on append

Further reading: Martin Fowler: DDD Aggregate

Practice questions

How does optimistic concurrency work when appending events?

Answer

The writer states the stream version it based its decision on; the store rejects the append if other events were appended since, and the command is retried with fresh state.

How are rules spanning multiple aggregates handled?

Answer

Through eventual consistency: policies or sagas react to events from one aggregate and issue commands to others, with compensation if needed.

Open in the app to mark lessons complete, take notes and track your progress.