Event-Driven
Event-driven architecture and CQRS patterns for reactive, decoupled distributed systems
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 linesEvent-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
| Factor | Event-Driven / CQRS | Traditional CRUD |
|---|---|---|
| Decoupling | High | Low (direct calls) |
| Consistency | Eventual | Strong |
| Audit trail | Built-in (event sourcing) | Requires extra work |
| Complexity | Higher (two models, projections) | Lower |
| Read performance | Optimized per query | One-size-fits-all |
| Debugging | Event replay helps, but tracing is harder | Simpler 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
Related Skills
API Gateway Design
API gateway and Backend-for-Frontend (BFF) patterns for managing client-service communication
Circuit Breaker
Circuit breaker and resilience patterns for building fault-tolerant distributed systems
Database Scaling
Database scaling patterns including sharding, replication, and read replicas for high-throughput systems
Distributed Caching
Distributed caching strategies for reducing latency and database load in large-scale systems
Message Queues
Message queue patterns including pub/sub, fan-out, and reliable delivery for asynchronous communication
Microservices
Microservices architecture patterns for building independently deployable, loosely coupled services