Skip to main content
Technology & EngineeringSystem Design106 lines

Event-Driven

Event-driven architecture and CQRS patterns for reactive, decoupled distributed systems

Quick Summary17 lines
You are an expert in Event-Driven Architecture and CQRS for designing scalable distributed systems.

## Key Points

- Topic-based: Kafka, AWS SNS — events published to topics, consumers subscribe.
- Queue-based: RabbitMQ, SQS — point-to-point delivery with acknowledgment.
- Log-based: Kafka, Pulsar — append-only log with consumer offsets for replay.
- Choreography: Each service reacts to events and emits new ones. No central coordinator. Works well for simple flows.
- Orchestration: A saga orchestrator directs the flow by sending commands and listening for responses. Better for complex, multi-step processes.
- Define a clear event schema and version it; use a schema registry to prevent breaking changes from propagating silently.
- Use the outbox pattern to guarantee event delivery without two-phase commits; never write to the database and publish to the broker in separate transactions.
- Build projections to be rebuildable — they should be derivable entirely from the event log, so you can fix bugs by replaying events.
- Treating events as commands ("DoSomething" instead of "SomethingHappened"); this reintroduces coupling because the producer dictates behavior.
- Ignoring eventual consistency in the UI — users may see stale data if the read model has not yet processed the latest events; design the UX to account for this.
- **God Event**: Publishing a single massive event type that contains everything about an entity. Consumers become coupled to the entire schema, and any field change breaks all subscribers.
skilldb get system-design-skills/Event-DrivenFull skill: 106 lines
Paste into your CLAUDE.md or agent config

Event-Driven Architecture & CQRS — System Design

You are an expert in Event-Driven Architecture and CQRS for designing scalable distributed systems.

Core Philosophy

Event-driven architecture is built on a simple but powerful inversion: instead of services telling each other what to do, they announce what happened and let interested parties react. This shift from imperative commands to declarative facts is what enables true decoupling — producers and consumers evolve independently because neither knows nor cares about the other's existence.

CQRS takes this further by acknowledging that reads and writes have fundamentally different performance characteristics, scaling requirements, and consistency needs. Trying to serve both through a single model creates compromises that satisfy neither. Separating them allows each side to be optimized independently — the write model enforces business invariants while read models are shaped exactly to the queries they serve.

The price of this architectural style is eventual consistency. Data propagates through the system asynchronously, and there will always be a window where different views of the system disagree. Designing for eventual consistency is not just a technical challenge — it requires rethinking the user experience, the testing strategy, and the team's mental model of how data flows through the system.

Overview

Event-driven architecture (EDA) structures systems around the production, detection, and reaction to events — immutable records of something that happened. CQRS (Command Query Responsibility Segregation) separates the write model (commands) from the read model (queries), often combined with event sourcing where the event log is the source of truth.

Core Concepts

Events as First-Class Citizens

An event is an immutable fact: OrderPlaced, PaymentProcessed, InventoryReserved. Events describe what happened, not what should happen. Producers emit events without knowledge of consumers.

[Command] --> [Write Model] --publishes--> [Event Store / Broker]
                                                |
                            +-------------------+-------------------+
                            v                   v                   v
                     [Read Model A]      [Read Model B]      [Analytics]
                     (list queries)      (search index)      (reporting)

Event Sourcing

Instead of storing current state, store the sequence of events that led to that state. Current state is derived by replaying events. This provides a complete audit trail and enables temporal queries ("what was the state at time T?").

CQRS Separation

Commands (writes) go through a domain model optimized for validation and business rules. Queries (reads) are served from denormalized projections optimized for specific read patterns. The two sides are connected by events.

Event Broker Topologies

  • Topic-based: Kafka, AWS SNS — events published to topics, consumers subscribe.
  • Queue-based: RabbitMQ, SQS — point-to-point delivery with acknowledgment.
  • Log-based: Kafka, Pulsar — append-only log with consumer offsets for replay.

Implementation Patterns

Event Choreography vs. Orchestration

  • Choreography: Each service reacts to events and emits new ones. No central coordinator. Works well for simple flows.
  • Orchestration: A saga orchestrator directs the flow by sending commands and listening for responses. Better for complex, multi-step processes.

Outbox Pattern

To avoid dual-write problems (writing to the database and publishing an event), write the event to an outbox table within the same database transaction, then a separate process publishes it to the broker. This guarantees at-least-once delivery.

Projections and Materialized Views

Event consumers build read-optimized views. A single event stream can power multiple projections: a search index, a reporting table, a cache. Projections can be rebuilt from scratch by replaying the event log.

Idempotent Consumers

Since events may be delivered more than once, consumers must handle duplicates. Use event IDs, deduplication tables, or design operations to be naturally idempotent.

Trade-offs

FactorEvent-Driven / CQRSTraditional CRUD
DecouplingHighLow (direct calls)
ConsistencyEventualStrong
Audit trailBuilt-in (event sourcing)Requires extra work
ComplexityHigher (two models, projections)Lower
Read performanceOptimized per queryOne-size-fits-all
DebuggingEvent replay helps, but tracing is harderSimpler state inspection

Choose EDA/CQRS when you need high decoupling, different read/write scaling, or a full audit log. Avoid it for simple CRUD applications where the added complexity is not justified.

Best Practices

  • Define a clear event schema and version it; use a schema registry to prevent breaking changes from propagating silently.
  • Use the outbox pattern to guarantee event delivery without two-phase commits; never write to the database and publish to the broker in separate transactions.
  • Build projections to be rebuildable — they should be derivable entirely from the event log, so you can fix bugs by replaying events.

Common Pitfalls

  • Treating events as commands ("DoSomething" instead of "SomethingHappened"); this reintroduces coupling because the producer dictates behavior.
  • Ignoring eventual consistency in the UI — users may see stale data if the read model has not yet processed the latest events; design the UX to account for this.

Anti-Patterns

  • Events as Commands: Naming events imperatively ("SendEmail", "UpdateInventory") instead of declaratively ("OrderPlaced", "PaymentReceived"). This reintroduces coupling because the producer dictates consumer behavior.

  • God Event: Publishing a single massive event type that contains everything about an entity. Consumers become coupled to the entire schema, and any field change breaks all subscribers.

  • Dual Writes Without Outbox: Writing to the database and publishing to the event broker in separate operations. If one succeeds and the other fails, the system enters an inconsistent state with no automatic recovery path.

  • Synchronous Disguised as Async: Publishing an event and then polling or blocking until the consumer processes it. This eliminates the latency and decoupling benefits of async communication while keeping all of its complexity.

  • Unbounded Event Replay: Rebuilding projections by replaying the entire event history without snapshots or checkpoints. As the event log grows, rebuild times become hours or days, making recovery from projection bugs impractical.

Install this skill directly: skilldb add system-design-skills

Get CLI access →