Event-Driven Architecture
How to design reactive systems where components communicate through events, achieving decoupling, scalability, and asynchronous processing.
What problem it solves
In a traditional architecture based on synchronous calls (request-response), services are tightly coupled: the sender must know the receiver, wait for its response, and handle its failures. This creates several problems:
- Temporal coupling: The sender is blocked until the receiver responds
- Knowledge coupling: The sender must know who processes its request
- Limited scalability: Adding a new consumer requires modifying the sender
- Fragility: If the receiver is down, the sender fails too
Synchronous communication (coupled):
Order Service βββΊ Inventory Service (waits for response)
βββΊ Payment Service (waits for response)
βββΊ Notification Service (waits for response)
βββΊ Analytics Service (waits for response)
Order Service knows ALL the services
If one fails, the order fails
Adding a service = modifying Order Service
How it works
Event-Driven Architecture (EDA) inverts the communication model. Instead of a service calling others directly, it publishes an event that describes something that happened. Interested services subscribe to those events and react independently.
Event-based communication (decoupled):
Order Service ββeventβββΊ Event Bus
"OrderCreated"
β
βββββββββββββΌββββββββββββ
βΌ βΌ βΌ
Inventory Payment Notification
Service Service Service
Order Service does NOT know the consumers
Each service reacts independently
Adding a service = subscribing to the event
Key concepts
| Concept | Description | Example |
|---|---|---|
| Event | Immutable record of something that happened | OrderCreated, PaymentProcessed |
| Producer | Service that publishes events | Order Service publishes OrderCreated |
| Consumer | Service that reacts to events | Inventory Service listens for OrderCreated |
| Event Bus/Broker | Infrastructure that transports events | Kafka, RabbitMQ, Amazon SNS/SQS |
| Topic/Queue | Channel through which events flow | orders.created, payments.processed |
Types of events
Domain Events: Represent business facts.
{
"type": "OrderCreated",
"timestamp": "2024-01-15T10:30:00Z",
"data": {
"orderId": "ORD-001",
"customerId": "USR-123",
"items": [{"productId": "P001", "quantity": 2}],
"total": 150.00
}
}
Integration Events: Communicate changes between bounded contexts.
{
"type": "InventoryReserved",
"source": "inventory-service",
"data": {
"orderId": "ORD-001",
"reservationId": "RES-456",
"status": "confirmed"
}
}
Delivery patterns
Pub/Sub (Publish-Subscribe): An event is delivered to all subscribers.
Producer βββΊ Topic βββΊ Consumer A
βββΊ Consumer B
βββΊ Consumer C
Event Streaming: Events are stored in a persistent log and consumers read at their own pace.
Producer βββΊ Persistent log (Kafka)
[event1, event2, event3, ...]
Consumer A: reading event 3 (up to date)
Consumer B: reading event 1 (behind, processing)
Consumer C: new, starts from event 1
Delivery guarantees
| Guarantee | Description | Use |
|---|---|---|
| At-most-once | The event is delivered at most once (may be lost) | Metrics, analytics |
| At-least-once | The event is delivered at least once (may be duplicated) | Most cases (with idempotency) |
| Exactly-once | The event is delivered exactly once | Financial transactions (hard to achieve) |
Advantages
- Decoupling: Producers and consumers do not know each other
- Scalability: Adding consumers does not require modifying producers
- Resilience: If a consumer is down, events accumulate and are processed when it comes back
- Asynchronous processing: The producer does not wait for consumers to finish
- Natural auditing: Events form a log of everything that happened in the system
- Extensibility: New features are added by subscribing to existing events
Trade-offs / Disadvantages
- Eventual consistency: Data is not updated instantly across all services
- Debugging complexity: Tracing the flow of an event across multiple services is hard
- Ordering: Guaranteeing the processing order of events requires careful design
- Duplicates: Consumers must be idempotent to handle duplicate events
- Additional infrastructure: You need a messaging broker (Kafka, RabbitMQ) along with its operation
- Complex testing: Testing asynchronous flows is harder than testing synchronous calls
When to use
- Systems where multiple services need to react to the same event
- When processing can be asynchronous (you donβt need an immediate response)
- Systems with high scalability and throughput requirements
- When you need to decouple services so they evolve independently
- Complex business flows with multiple steps (orders, payments, shipping)
- When you need an audit log of everything that happens in the system
When to avoid
- Operations that require an immediate synchronous response (user queries)
- Simple systems with few services and linear flows
- When strong consistency is a non-negotiable requirement
- Teams without experience in distributed systems and asynchronous debugging
- When you canβt justify the infrastructure of a messaging broker
Common technologies and implementations
| Category | Options |
|---|---|
| Event Streaming | Apache Kafka, Amazon Kinesis, Azure Event Hubs |
| Message Brokers | RabbitMQ, Amazon SQS/SNS, Google Pub/Sub, NATS |
| Event Store | EventStoreDB, Kafka (as an event store) |
| Frameworks | Spring Cloud Stream, MassTransit (.NET), Axon Framework |
| Observability | OpenTelemetry, Jaeger, Zipkin (distributed tracing) |
Relationship with other patterns
- Saga Pattern: Uses events to coordinate distributed transactions across services
- Outbox Pattern: Guarantees reliable publishing of events together with database changes
- CQRS: Separates reads and writes using events to keep the models in sync
- Idempotency: Consumers must be idempotent to handle duplicate events
- Circuit Breaker: Protects consumers from overload when there are event spikes
Next steps
Event-Driven Architecture is the foundation of communication in modern microservices. To protect integration with external systems that donβt speak your event language, explore the Anti-Corruption Layer. To ensure that consumers handle duplicates correctly, review the Idempotency pattern.