principle-event-driven · git:20260828.71a6399 · 2026-08-28 · sha256 78a47840f7714c50

principle-event-driven git:20260828.71a6399A

Immutable. This exact content never changes and is served at /api/v1/blob/78a47840f7714c50.

---
name: principle-event-driven
description: Event-driven architecture — event sourcing, CQRS, sagas, schema evolution, consumer groups, partitions, idempotent handlers, outbox pattern, dead letter queues. Auto-load when designing event-driven systems, evaluating event sourcing or CQRS, planning saga workflows, implementing idempotent consumers or the outbox pattern, managing dead letter queues, or assessing whether event-driven architecture fits the problem.
---
<!-- preload-canary: SWB-PRELOAD-PRINCIPLE-EVENT-DRIVEN -->

# Event-Driven Architecture

The log is the source of truth: producers emit immutable events; consumers react asynchronously. This decouples services at deployment and operational boundaries and lets new consumers replay history — at the cost of eventual consistency and significantly more failure surface area than synchronous calls.

## Event Sourcing

Store every state change as an immutable event; derive current state by replaying the log. The aggregate's current state is a projection, not the record of truth.

- Snapshots cap replay cost: persist a projected state checkpoint every N events and replay only from the latest snapshot.
- Projections are disposable — design them to be rebuilt by replaying; avoid hand-rolled caches that drift from the log.
- Event store must be append-only; mutating or deleting events destroys the audit trail and breaks replaying consumers.
- GDPR erasure: store PII in a side-table and tombstone the key rather than mutating event history.

## CQRS (Command Query Responsibility Segregation)

Separate the write model (commands that mutate state) from the read model (queries over projections). Often paired with event sourcing but independent of it.

- Read replicas can be denormalized and tuned for specific query shapes without polluting the write model.
- Consistency lag between write and read models is a feature contract, not a bug — document the SLA.
- Avoid CQRS in simple CRUD services; indirection cost exceeds benefit until query and write shapes diverge significantly.

## Sagas — Choreography vs Orchestration

Long-running transactions spanning services must be modeled as sagas with explicit compensation steps.

**Choreography** — each service emits events and reacts to others; no central coordinator.
- Low coupling; hard to trace and debug as the workflow grows beyond two or three participants.

**Orchestration** — a central saga orchestrator drives the workflow and issues commands to participants.
- Easier observability and rollback reasoning; introduces a single point of coordination and coupling.
- Prefer orchestration when compensation logic is non-trivial or the workflow has more than three participants.

Compensation steps must be idempotent — the orchestrator may retry them on failure.

## Event Schema Evolution

Consumers and producers deploy independently; schema drift causes silent deserialization failures.

- **Backward-compatible changes** (add optional field): safe to deploy producer first.
- **Conditionally breaking** (add enum value): safe only if all consumers tolerate unknown values (e.g., JSON Schema with a catch-all default); for Avro or Protobuf under BACKWARD compatibility, treat as breaking — require a versioned event type and a dual-publish period.
- **Breaking changes** (rename field, remove field, change type): require a versioned event type and a dual-publish period.
- Prefer a schema registry (Confluent Schema Registry, AWS Glue) over ad-hoc JSON to enforce compatibility rules at publish time.
- Never rely on field ordering; always deserialize by name.

## Consumer Groups, Partitions, and Ordering

- Ordering is guaranteed only within a partition; partition by a key that co-locates events that must be ordered (e.g., all events for a given `entity_id` on the same partition).
- Consumer group parallelism is bounded by partition count — you cannot have more active consumers than partitions.
- Rebalance storms on group membership changes cause in-flight delays; use static group membership where supported.
- Cross-partition ordering requires application-level sequencing (vector clocks or monotonic sequence numbers).

## Idempotent Handlers and the Outbox Pattern

At-least-once delivery is the broker default; consumers must handle duplicate events without producing duplicate side effects.

- Use a stable event ID as the deduplication key; see `principle-api-design#Idempotency` for the storage pattern.
- The **outbox pattern** ensures atomicity between a database write and event publication: write the event to an `outbox` table in the same transaction as the domain change; a relay process publishes it to the broker. Eliminates the dual-write problem.
- For idempotency key design see `principle-api-design#Idempotency`; for retry budgets before DLQ routing see `principle-error-handling#Retries and Backoff`.

## Dead Letter Queues and Poison Messages

A dead letter queue (DLQ) is the fail-soft path for unprocessable messages — see `principle-resiliency#Fail-Fast vs Fail-Soft`.

- Always configure a DLQ; without one a poison message halts the entire partition indefinitely.
- Emit a structured alert on every DLQ write — silent DLQs hide data loss.
- Preserve the original payload, headers, and metadata so messages are replayable after the root cause is fixed.
- Define a replay SLA: stale DLQ messages replayed into a later system state may apply out-of-order effects.

## When Event-Driven Architecture is Overkill

- Simple CRUD with a single service and database; REST or RPC is cheaper and easier to reason about.
- Strong consistency required across the operation — sagas with compensation add complexity without benefit.
- Team has no operational experience with brokers; the operational burden exceeds the decoupling gain.
- Low event volume (fewer than hundreds per day); a message broker adds infrastructure cost for no throughput benefit.

## Red Flags

| Flag | Problem |
|------|---------|
| No partition key strategy | Events for the same entity land on different partitions; ordering lost |
| Dual-write without outbox | Crash between DB write and broker publish causes silent data loss |
| No DLQ configured | Poison message halts the partition; data backs up without alerting |
| Schema changes without versioning | Consumers fail silently on unexpected field shapes |
| Choreography saga with no compensating events | Partial failures leave distributed state permanently inconsistent |
| Idempotency check absent on consumer | At-least-once delivery produces duplicate side effects |
| Cross-partition ordering assumed without sequencing | Ordering guarantee breaks silently across partitions or brokers |