Event Sourcing with Redis Streams

+15 Mana ✨

Introduction

Event sourcing is an architectural pattern where the state of an entity is derived entirely from a sequence of immutable events — rather than stored as a mutable record. Redis Streams map naturally to this pattern.

Key Concepts

  • Event store: the append-only log (Redis Stream) that holds all state-change events
  • Immutability: stream entries are not modified after creation; current state is always derived by replaying
  • Replay: processing all events in order via XRANGE to reconstruct current state
  • Snapshot: a cached state at a known stream ID to speed up replay for long streams

Real World Context

Banking: account balance is derived from a stream of transactions (deposit, withdrawal). The stream IS the source of truth — not a balance column.

Deep Dive

Instead of: UPDATE orders SET status = 'shipped' WHERE id = 1

You do: XADD order:1 '*' event OrderShipped shippedAt 2024-01-15T10:00:00Z

The current state is reconstructed by replaying all events in order:

OrderPlaced → OrderPaid → OrderShipped → (current state: shipped)
bash
# Append events as they happen
XADD order:1 '*' event OrderPlaced customerId 42 amount 99.95
XADD order:1 '*' event OrderPaid paymentId pay-7
XADD order:1 '*' event OrderShipped trackingId TRK-123

# Reconstruct state: replay all events
XRANGE order:1 - +
# Process each event in sequence to build current state

Why Streams fit event sourcing:

  • Immutable: entries cannot be modified (only deleted, which is discouraged in event sourcing)
  • Ordered: IDs guarantee chronological order
  • Persistent: history is never lost
  • Replayable: XRANGE lets you rebuild state from any point

Common Pitfalls

  • Storing mutable state alongside events: if you also keep a current-state field, it can diverge from the event log
  • Using XDEL: deleting events corrupts history; use soft-delete events (e.g., OrderCancelled) instead
  • Not handling schema evolution: event shapes change over time; consumers must handle both old and new formats

Best Practices

  • Use past-tense event names (OrderPlaced, not PlaceOrder) to convey immutability
  • Take periodic snapshots to bound replay time for long-lived entities
  • Version your event schema from day one to make future migrations easier

Summary

Event sourcing treats every state change as an immutable event appended to a log. Redis Streams provide the persistence, ordering, and replay capabilities needed. Current state is always derived, never stored — reducing write conflicts and enabling full history.

Code Examples

bash
# Append domain events
XADD account:1001 '*' event MoneyDeposited amount 500 balance 500
XADD account:1001 '*' event MoneyWithdrawn amount 100 balance 400
XADD account:1001 '*' event MoneyDeposited amount 250 balance 650

# Replay to reconstruct current balance
XRANGE account:1001 - +
# Apply each event to derive: balance = 650

# Reconstruct state at a specific point in time
XRANGE account:1001 - 1691234567890 +
# Balance before that timestamp
✓ Completed