Reference Architecture: Event-driven / CQRS
In an event-driven architecture, services publish events to an event log (e.g. Kafka) instead of calling each other directly, and consumers process them asynchronously. Starting from building the event backbone, this tree walks step by step through event sourcing (deriving state from events), command/query separation with CQRS, read model projection, idempotency, tolerance for eventual consistency and the 'exactly-once delivery' fallacy. The goal is a loosely coupled, scalable system whose consistency trade-offs are well understood.
Which decision are you facing in your event-driven architecture?
The foundation of an event-driven system is a durable event log that connects services through asynchronous events. Are you still building the backbone, do you want to derive state from events (event sourcing), or are you separating the read and write paths (CQRS)?
- I am building the event backbone that connects services
- I want to derive state from the event history
- I want to separate the read and write paths (CQRS)
How will you build the read side in CQRS, and what is your consistency tolerance?
In CQRS, the command (write) and query (read) sides are separated; write events update the projection that feeds the read model. The key question: can you tolerate the read model lagging behind writes (eventual consistency), or do you expect every event to be processed exactly once?
- I will feed the read model from events via a projection
- Reads must be current; lag is unacceptable
- I want events processed 'exactly once'
Recommendation: A durable event log backbone
At the center of an event-driven architecture is a durable, ordered event log that producers write to and consumers read at their own pace. This backbone decouples services in time and scale.
Recommendation: Event sourcing (derive state from events)
In event sourcing you store not the current state of an entity but all (immutable) change events applied to it; the current state is derived by applying these events in order. It provides a full audit history and the ability to return to any point in the past.
Recommendation: Read model projection
In CQRS the write side produces events; a projection listens to these events and updates a query-optimized read model (denormalized view). Reads are served from this ready model, completely separated from the write path, and both sides scale independently.
Recommendation: Design for eventual consistency tolerance
In event-driven/CQRS systems the read model lags slightly behind writes (eventual consistency). Rather than trying to 'eliminate' this, design to tolerate it; keep the few flows that require strict immediate consistency separate.
Recommendation: Idempotency (the exactly-once fallacy)
In distributed messaging, end-to-end 'exactly-once delivery' is a fallacy in practice; message duplicates are unavoidable because of the network and retries. The right goal is idempotent processing: processing the same event twice must give the same result as processing it once.