DATABASE-ADVANCED Contents

Event-Driven Modeling

Events, projections, and schema evolution patterns that scale organizationally.

On this page

Event-Driven Modeling: Design for Replay, Not Just for Today

Event-driven modeling treats state changes as events. Instead of modeling only the current state, you model the sequence of changes that produced it. This enables auditability, rebuildable read models, and better integration boundaries, but introduces ordering and operational complexity.

Production rule: if you adopt events, you must design for replay and idempotency from day one.

State Tables vs Event Log

Traditional modeling stores current state:

  • orders table has current status

Event-driven modeling stores changes:

  • OrderCreated, OrderPaid, OrderShipped events

You can still keep a current-state table, but it becomes a projection derived from events.

Append-Only: Why It Matters

Events are typically append-only. You do not mutate old events; you add new events.

Benefits:

  • Audit trail for free
  • Rebuildable projections
  • Debugging via history

Costs:

  • Data volume grows continuously
  • Need retention/archival strategy
  • Queries on current state require projections

Outbox Pattern: Make Events Transactional

The hardest problem is ensuring you do not lose events or emit events for writes that never committed.

Outbox pattern:

  • Write domain state changes
  • Write corresponding event row into an outbox table in the same DB transaction
  • A publisher process reads outbox and publishes to the event bus

Production advantage: DB commit is the source of truth for whether an event exists.

Idempotency: Events Will Be Delivered More Than Once

Most event systems provide at-least-once delivery. Consumers must be idempotent.

Practical approach:

  • Each event has a unique id (event_id)
  • Consumer stores processed event_id (or last offset per partition)
  • Duplicate deliveries are ignored

Failure mode: consumers apply the same event twice (double charge, double increment).

Ordering: Define What Must Be Ordered

Global ordering is expensive or impossible. Instead, define ordering per entity:

  • All events for order_id must be processed in order
  • Events across different orders can be processed concurrently

Production rule: design event partitioning/routing to preserve per-entity order.

Exactly-Once Illusion

Teams often chase “exactly-once.” In production, it is usually achieved by combining:

  • At-least-once delivery
  • Idempotent consumers
  • Transactional boundaries where needed (outbox)

This is practical and reliable without requiring a mythical perfect transport.

Read Models (CQRS): Optimize for Queries

Events are great for integration, but most applications need fast reads. Build projections:

  • OrderSummary table for UI
  • UserBalance table derived from payment events
  • Search index derived from content events

Projection design rules:

  • Projections must be rebuildable from event history
  • Projection updates must be idempotent
  • Projection lag is a first-class metric

Replay and Backfill: The Real Superpower (and Risk)

Because you have events, you can replay to rebuild projections or fix bugs.

But replay is also dangerous:

  • Can overload downstream systems
  • Can re-trigger side effects if consumers are not properly separated

Production patterns:

  • Separate “projection consumers” from “side effect consumers”
  • Use replay-safe modes (no external calls during replay)
  • Throttle replay and monitor progress

Schema Evolution for Events

Events are contracts. Changing them breaks consumers.

Rules:

  • Prefer additive changes (new fields) over breaking changes
  • Version event schemas explicitly
  • Keep backward compatibility for a defined window

Failure Modes in Production

  • Lost events: emitting events outside the DB transaction.
  • Ghost events: event emitted but DB write rolled back.
  • Duplicate side effects: non-idempotent consumer.
  • Ordering bugs: out-of-order processing breaks invariants.
  • Projection lag: read model becomes stale and user experience breaks.
  • Replay disaster: replay triggers external calls and doubles actions.
  • Contract break: event schema change breaks downstream services.

Operational Checklist

  • Use outbox (or CDC) to make event emission transactional with DB writes.
  • Make every consumer idempotent; store processed event ids/offsets.
  • Define ordering scope (per-entity ordering) and enforce partitioning accordingly.
  • Build projections for hot queries; measure projection lag.
  • Ensure projections are rebuildable; rehearse rebuild in staging.
  • Design replay mode that avoids external side effects.
  • Implement throttling for replay/backfill to protect downstream systems.
  • Version event schemas; prefer additive evolution.
  • Monitor outbox backlog, consumer lag, error rate, and retry storms.
  • Document event contracts and ownership (who produces, who consumes).

Summary

Event-driven modeling improves auditability and scalability by representing state changes as append-only events. In production, it only works if you treat events as contracts, emit them transactionally (outbox), build idempotent consumers, define ordering scope, and design for replay/backfill without causing side effects. The operational model is as important as the schema.