What is Event-Driven Architecture? How EDA works with events, CQRS, and event sourcing
Event-driven architecture (EDA) is a software design pattern where components communicate by producing and detecting events. Learn how EDA works, its topologies, event sourcing, CQRS, and when to use it.
On this page
- What is an event?
- Core components: emitters, channels, consumers
- Event topologies
- Event types
- Event sourcing: storing state as a sequence of events
- CQRS: separating reads from writes
- How EDA connects to other patterns
- When to use event-driven architecture
- Common challenges
- FAQ
- What is the difference between EDA and message queues?
- What is the difference between event sourcing and CQRS?
- When should you not use event-driven architecture?
- What are domain events and integration events?
- Sources
In a traditional web application, one service calls another directly: the caller sends a request and waits for a reply. That works fine until your system grows, your services multiply, and a single user action needs to trigger ten different downstream things — email notifications, audit logs, analytics pipelines, inventory updates. Chaining all those calls synchronously turns your architecture into a spider web of dependencies where one slow service drags everything else down.
Event-driven architecture (EDA) takes a different approach. Instead of services calling each other, they broadcast signals called events. Any service that cares about a particular event can subscribe and react. The producer of the event does not know or care how many consumers are listening — it just announces "something happened" and moves on.
This guide explains what EDA is, how events are structured, what topologies exist, and how it connects to related patterns like event sourcing and CQRS.
What is an event?
An event is a record of something that happened. Wikipedia defines it as "a significant change in state." For example, when a customer places an order, the event might be OrderPlaced with a payload like { orderId: "1234", customerId: "user_99", total: 49.99, items: [...] }. The event does not carry instructions — it simply reports a fact.
This distinction matters: the producer says "an order was placed" without knowing whether anyone is listening. Consumers decide for themselves what to do with that information — send a confirmation email, update inventory, trigger a fraud check, or all three at once.
Core components: emitters, channels, consumers
Every event-driven system has three building blocks:
Event emitters (producers) detect facts and publish them. An emitter has no knowledge of consumers — it does not know whether anything is listening, or what will happen next. This loose coupling is the core advantage of EDA.
Event channels are the conduits that carry events from emitters to consumers. Channels can be simple queues (first-in, first-out), publish/subscribe topics (one event broadcast to many consumers), or complex event streams. The channel decides where each event goes; producers and consumers do not need to know about each other.
Event consumers (sinks) subscribe to channels and react to events. A consumer might update a database, call an external API, send a notification, or simply log the occurrence. Consumers are independent of each other — one consumer failing does not block another.
Event topologies
There are two primary EDA topologies.
Broker topology — components broadcast events directly to a central broker (such as Apache Kafka, RabbitMQ, or Cloudflare Queues) without a central orchestrator. Consumers subscribe to topics and process events as they arrive. This topology offers high throughput and scalability because there is no single point of control. It works well when consumers are independent and can handle events at their own pace.
Mediator topology — a central orchestrator (a workflow engine or event mediator) coordinates event flows. When an initial event arrives, the mediator triggers a sequence of downstream steps. This topology gives you better control over complex, multi-step workflows where order matters. However, the mediator becomes a single point of coordination, which adds latency and a potential bottleneck.
Many real systems use a hybrid: broker topology for most events, mediator topology for complex workflows that require sequencing.
Event types
Not all events are the same. According to event-driven architecture literature (Yann Cui, Microsoft Azure patterns), events fall into two key categories:
Domain events represent important occurrences within a specific bounded context. They are typically lightweight — containing only the data consumers need — because they are consumed by services within the same domain. An example is ItemAddedToCart in an e-commerce domain.
Integration events communicate changes across bounded contexts or to external systems. They carry richer payloads because consumers are outside your direct control and may need more context. An example is OrderShipped, which an external logistics partner needs to arrange pickup.
Event sourcing: storing state as a sequence of events
Traditional systems store the current state of data — you save the row, you overwrite it on updates. Event sourcing flips this: instead of storing what the data is, you store every change that was made. Each change is an immutable event appended to an event log.
Martin Fowler's original definition: "Event Sourcing ensures that all changes to application state are stored as a sequence of events. Not only can we query these events, we can also use the event log to reconstruct past states, and as a foundation to automatically adjust the state to cope with retroactive changes."
Concretely, an e-commerce system using event sourcing would not store order.status = "shipped". Instead it stores the events: OrderCreated, PaymentReceived, OrderPacked, OrderShipped. The current state is derived by replaying the event log. This gives you three superpowers that traditional CRUD cannot match:
- Complete audit trail — every state change is captured with its timestamp and context. You can replay exactly what happened and when.
- Temporal queries — you can ask "what was the order total at 3 PM yesterday?" by replaying events up to that timestamp. No separate snapshot tables needed.
- Easy integration — new services can subscribe to the event stream and build their own read models without touching the write side.
The downside is complexity. Reconstructing state by replaying thousands of events is slow. For this reason, event-sourced systems typically maintain materialized views — pre-computed read models that are updated as events arrive. This is where CQRS fits in.
CQRS: separating reads from writes
CQRS (Command Query Responsibility Segregation) is a pattern that separates the code that writes data from the code that reads it. In a traditional CRUD system, the same model handles both. As Microsoft Azure's pattern documentation explains, CQRS is useful when "read and write operations have different performance and scaling requirements."
The write side (the command side) handles operations that change state — creating an order, cancelling a subscription, updating a profile. These are called commands, not queries, because they represent intent, not just data retrieval.
The read side (the query side) handles data retrieval. In an event-sourced system, the read side is built from materialized views of the event log. The same event OrderPlaced might update an order summary view for the UI, a revenue report view for finance, and a shipping queue view for logistics — all from one event.
CQRS pairs naturally with event sourcing. Event sourcing gives you an immutable event log as your source of truth; CQRS lets you project that log into as many read models as you need, each optimized for a specific query pattern.
How EDA connects to other patterns
EDA is not isolated — it builds on and connects to several other architectural patterns:
- Message queues (RabbitMQ, Kafka, Cloudflare Queues) are the infrastructure layer that EDA uses to transport events. A message queue is an event channel. The message-queues-explained pillar covers this in detail.
- Webhooks are a lightweight, HTTP-based form of event notification. A webhook is essentially an event sent over HTTP to a specific endpoint. Webhooks-explained covers this pattern.
- Microservices and EDA are natural partners: loosely coupled services that react to events scale independently and deploy without coordination. Monolith vs microservices covers the trade-offs.
- Serverless functions (Lambda, Cloudflare Workers) respond to events — an HTTP request, a timer, a queue message — making them a natural fit for event consumers in an EDA.
When to use event-driven architecture
EDA shines in four scenarios:
- Systems with multiple independent consumers — when one action needs to trigger many downstream reactions (notifications, logging, analytics, integrations), EDA eliminates the need for the producer to know about all of them.
- Microservices that need loose coupling — services that communicate only through events can be deployed, scaled, and updated independently. No service calls another; they just publish and subscribe.
- Audit-critical applications — financial systems, compliance tools, healthcare records. Event sourcing gives you a tamper-evident log of every state change.
- Temporal querying and historical reconstruction — when you need to know not just the current state but how you got there, or what the state was at a point in time.
EDA is overkill for simple request-response applications where one service calls another and waits. The additional infrastructure (brokers, event stores, materialized views) adds operational complexity. If your system has no need for multiple consumers, loose coupling, or audit trails, a synchronous architecture is simpler and faster to build.
Common challenges
Eventual consistency — in an EDA, consumers process events asynchronously. The read side may lag behind the write side by milliseconds or seconds. If your user needs to see the result of a write immediately (read-your-writes consistency), you need to account for this — either by reading from the write model directly after a command, or by designing your UI to show the expected outcome optimistically.
At-least-once delivery — message brokers typically guarantee that an event will be delivered at least once, not exactly once. A consumer might receive the same event twice (for example, after a failed processing attempt that retried). Consumers must be idempotent — processing the same event twice must produce the same result as processing it once. Microsoft Azure's pattern documentation gives the example of a seat-reservation system: a duplicate reservation event must result in only one seat being decremented from availability.
Event schema evolution — as your system changes, event shapes change too. A OrderPlaced event from two years ago might have different fields than one created today. Strategies include semantic versioning of events, upcasters (adapters that translate old event schemas to new ones), and storing only IDs in events and fetching the full payload on demand.
FAQ
What is the difference between EDA and message queues?
Event-driven architecture is a design pattern — it describes how systems communicate via events. Message queues (RabbitMQ, Kafka, Cloudflare Queues) are the infrastructure that implements the pattern. You can have a message queue without EDA (for example, a simple task queue with one producer and one consumer). You can have EDA without a traditional message queue if you use HTTP webhooks or database triggers. But in practice, most EDA systems use message queues as their event channels.
What is the difference between event sourcing and CQRS?
Event sourcing is a storage pattern: you store every state change as an immutable event. CQRS is a architectural pattern: you separate the code that writes data from the code that reads data. They are complementary — event sourcing provides a natural event stream to feed CQRS read models. But you can use CQRS without event sourcing (using a traditional database for writes and a separate read-optimized store for queries), and you can use event sourcing without CQRS (replaying events into a single, traditional read model).
When should you not use event-driven architecture?
Avoid EDA when your system is simple and has no need for multiple independent consumers, audit trails, or temporal queries. The benefits of loose coupling and scalability come with significant operational complexity: you need to run and monitor message brokers or event stores, handle eventual consistency, and write idempotent consumers. For a straightforward CRUD application with one service and one database, a synchronous request-response model is faster to build and easier to operate.
What are domain events and integration events?
Domain events represent important occurrences within a specific bounded context. They have lightweight payloads because consumers are within the same service domain. Integration events communicate changes across bounded contexts or to external systems. They carry richer payloads because consumers outside your direct control may need more context. Both types are first-class citizens in EDA; the distinction helps you design appropriately sized event payloads.
Sources
Sources
- Wikipedia — Event-driven architecture (en.wikipedia.org/wiki/Event-driven_architecture
- CC BY-SA 4.0)
- Wikipedia — Event sourcing (en.wikipedia.org/wiki/Event_sourcing
- CC BY-SA 4.0)
- Microsoft Learn — CQRS Pattern (learn.microsoft.com/en-us/azure/architecture/patterns/cqrs
- Microsoft open)
- Martin Fowler — Event Sourcing (martinfowler.com/eaaDev/EventSourcing.html)
- Chris Richardson — Event sourcing pattern (microservices.io/patterns/data/event-sourcing.html)
Share this guide
Related guides
- What is a message queue? How message queues work and when to use them
A message queue is a software component that lets services exchange data asynchronously. Learn how queues, topics, publishers and consumers work, and when they are worth the complexity.
- Microservice Architecture Explained — Decomposition, Communication, Data, and Trade-offs
Microservice architecture splits an application into small independent services. Learn how decomposition, inter-service communication, data management, and the microservice premium work.
- What is a database transaction? ACID properties explained
A database transaction bundles several operations into one all-or-nothing unit. This guide explains transactions, the ACID properties (atomicity, consistency, isolation, durability), and isolation levels.