
How Event-Driven Architecture Works — Reacting Instead of Polling
In a traditional request-driven system, service A calls service B and waits for a response. Service A knows about service B. If B is slow or down, A is affected. The two services are coupled in time, availability, and knowledge of each other.
Event-driven architecture inverts this. Instead of calling other services directly, a service emits an event describing what happened. Other services that care about that event react to it. The producer does not know who consumes its events, or even if anyone does.
Events, Commands, and Queries
These three concepts look similar but serve different purposes:
Event — a notification that something happened. Past tense. Immutable. "OrderPlaced", "UserRegistered", "PaymentFailed". The producer states a fact. It does not ask for anything.
Command — a request for something to happen. Imperative. "PlaceOrder", "SendEmail", "ChargeCard". Directed at a specific handler. Expects the handler to act.
Query — a request for information. "GetOrderById", "ListUsers". Expects a response. No side effects.
Events are the foundation of event-driven architecture. A service performs its work, commits its data, then emits an event. Downstream services subscribe to events they care about and react accordingly.
Producers and Consumers
An event producer is any component that emits events. The order service completes an order and emits OrderPlaced. It doesn't know or care what happens next.
An event consumer subscribes to events and processes them. Multiple consumers can react to the same event:
- The inventory service receives
OrderPlacedand decrements stock. - The email service receives
OrderPlacedand sends a confirmation. - The analytics service receives
OrderPlacedand updates dashboards.
Adding a new consumer does not require changing the producer. This is the core benefit: components are decoupled. The order service doesn't import the email service. It doesn't know the email service exists. You can add, remove, or replace consumers without touching the producer.
The Event Bus
Producers and consumers need something in between — an event bus or message broker that receives events from producers and delivers them to consumers.
Common implementations:
| Broker | Model | Durability | Use case |
|---|---|---|---|
| Apache Kafka | Distributed log | Events stored on disk, replayable | High-throughput, event sourcing, streaming |
| RabbitMQ | Message broker | Messages acknowledged and deleted | Task queues, routing, RPC |
| Redis Streams | In-memory log | Persistent with append-only log | Lightweight streaming, real-time |
| Amazon SNS/SQS | Cloud-managed | Managed durability | AWS-native, fan-out + queue |
The broker decouples producers from consumers in three ways:
- Time — the producer emits the event and moves on. The consumer processes it later, maybe seconds later, maybe hours later.
- Availability — if a consumer is down, the broker holds the event. When the consumer recovers, it catches up.
- Knowledge — the producer publishes to a topic. It does not address specific consumers.
Eventual Consistency
In a request-driven system, you can wrap multiple operations in a transaction — either everything succeeds or nothing does. In an event-driven system, there is no single transaction boundary across services.
Instead, you get eventual consistency. The order service commits the order. It emits OrderPlaced. The inventory service eventually processes that event and decrements stock. Between the order being placed and the stock being decremented, the system is in an inconsistent state. The inventory count is temporarily wrong.
This is acceptable in most systems because the inconsistency window is small (milliseconds to seconds) and the tradeoff is worth it — services remain independent and available. A system that requires perfect consistency across services for every operation should probably not be split into microservices for those operations.
For a deeper treatment of consistency models, see How Consistency Works.
Event Ordering and Delivery Guarantees
Not all brokers provide the same guarantees:
At-most-once — the event is delivered zero or one times. If the consumer crashes before acknowledging, the event is lost. Fast but unreliable.
At-least-once — the event is delivered one or more times. If the consumer crashes before acknowledging, the broker redelivers. The consumer must be idempotent — processing the same event twice must produce the same result.
Exactly-once — the event is delivered exactly one time. Hard to achieve in distributed systems. Kafka provides exactly-once semantics within its ecosystem, but across system boundaries, at-least-once with idempotent consumers is the practical standard.
Ordering — Kafka guarantees ordering within a partition. RabbitMQ guarantees ordering within a single queue. Global ordering across all events is expensive and rarely necessary. Design for per-entity ordering (all events for order #123 arrive in order) rather than global ordering.
When to Use Event-Driven Architecture
Good fits:
- Decoupling services that don't need synchronous responses.
- Async processing — sending emails, generating reports, updating search indexes.
- Audit trails — events are a natural log of everything that happened.
- Multiple consumers need to react to the same occurrence.
Poor fits:
- Operations that need an immediate, synchronous response (use REST or gRPC).
- Simple CRUD applications where the overhead of a message broker is not justified.
- Teams without the operational capacity to run and monitor a message broker.
The Pattern in Practice
A typical flow: a user places an order on an e-commerce site.
- The order service validates the request, writes the order to its database, and publishes
OrderPlacedto the event bus. - The payment service consumes
OrderPlaced, charges the card, and publishesPaymentProcessedorPaymentFailed. - The inventory service consumes
OrderPlacedand reserves stock. - The notification service consumes
PaymentProcessedand sends a receipt.
Each service operates independently. If the notification service is down for maintenance, orders still process. When the notification service comes back, it catches up on missed events and sends the receipts.
Next Steps
- How Pub/Sub Works — the publish-subscribe pattern that underpins most event-driven systems.
- How Message Queues Work — buffering work between producers and consumers.
- How CQRS Works — separating read and write paths, often paired with event-driven architecture.
Prerequisites
Referenced by
- How Caching Works — Speed vs Freshness at Every Layer
- Software Architecture FAQ
- How Message Queues Work — Buffering Work Between Services
- What is a Microservice
- What is Event-Driven Architecture
- What is a Message Broker
- What is Pub/Sub
- What is Event Sourcing
- How Pub/Sub Works — Decoupling Publishers from Subscribers
- How Microservices Work — From Monoliths to Independent Services