Metadata
| Field | Value |
|---|---|
| Slug | event-driven-architecture |
| Difficulty | Advanced |
| Estimated Reading Time | 18 min |
| Estimated Coding Time | 25 min |
| Tier | 3 — Distributed Systems |
| Implementation Language | Java |
| SEO Description | Master Event-Driven Architecture (EDA) for microservices. Learn Event Sourcing, CQRS, Choreography, Orchestration, and see a Java implementation. |
1. Overview
What problem does it solve?
In a traditional Request-Driven architecture, when a user buys a product, the Order Service makes synchronous HTTP calls to the Inventory Service (to reduce stock) and the Shipping Service (to print a label).
This creates a web of tight coupling. The Order Service has to know the IPs, APIs, and data models of everyone else. If the Shipping Service is down, the whole checkout process fails.
Event-Driven Architecture (EDA) flips this. The Order Service simply shouts into a megaphone: "An order was placed!" (An Event). It doesn't know or care who is listening. The Inventory Service and Shipping Service independently listen for that event and react to it.
What breaks without it?
- Agility: Adding a new feature (like a
Rewards Service) requires modifying the coreOrder Servicecode to make another HTTP call. - Availability: Synchronous chains of microservices are incredibly fragile. If any link in the chain breaks, the whole system fails.
- Performance: The user has to wait for all downstream services to finish before getting a response.
2. Motivation
The shift to EDA was largely driven by the limitations of traditional monolithic databases and synchronous REST APIs at massive scale (e.g., Netflix, LinkedIn, Uber).
LinkedIn created Kafka specifically to move away from batch processing to real-time event streaming. Martin Fowler popularized advanced EDA concepts like Event Sourcing and CQRS to solve the problem of managing state and auditing in highly distributed systems.
3. Real-World Usage
| System | Use Case |
|---|---|
| Uber | Driver location updates, trip state changes, and pricing calculations are all real-time events flowing through Kafka. |
| Netflix | Viewing history and recommendations are updated asynchronously via events. |
| E-commerce Checkout | Order placement triggers events for inventory, payment, shipping, and email services. |
| IoT Systems | Millions of sensors publishing telemetry events for downstream processing. |
4. Prerequisites
| Concept | Block |
|---|---|
| Message Brokers | 024 Message Queues & Pub/Sub |
5. Visual Explanation
Request-Driven vs Event-Driven
%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#ffcc00', 'edgeLabelBackground':'#ffffff'}}}%%
graph TD
classDef svc fill:#f9f9f9,stroke:#333,stroke-width:2px;
classDef broker fill:#d4edda,stroke:#28a745,stroke-width:2px;
subgraph Request-Driven (Synchronous)
O1[Order Service]:::svc -- "HTTP POST" --> I1[Inventory Service]:::svc
O1 -- "HTTP POST" --> S1[Shipping Service]:::svc
end
subgraph Event-Driven (Asynchronous)
O2[Order Service]:::svc -- "Publish Event:<br/>OrderPlaced" --> B{{Event Broker<br/>Kafka / RabbitMQ}}:::broker
B -- "Consume" --> I2[Inventory Service]:::svc
B -- "Consume" --> S2[Shipping Service]:::svc
end
Choreography vs Orchestration
When a business workflow requires multiple steps (Order -> Pay -> Ship), how is it managed?
-
Choreography (Decentralized): Like dancers reacting to music.
- Order Service emits
OrderCreated. - Payment Service hears it, charges card, emits
PaymentSucceeded. - Shipping Service hears
PaymentSucceeded, prints label. - Pros: Perfectly decoupled.
- Cons: Extremely hard to track the overall status. Debugging requires tracing tools.
- Order Service emits
-
Orchestration (Centralized): Like a conductor directing an orchestra.
- An Order Orchestrator Service commands the others: "Payment, charge the card!" -> waits for reply -> "Shipping, print the label!".
- Pros: Easy to monitor and manage complex state.
- Cons: The Orchestrator becomes a god-service and a single point of failure.
6. Internal Working
Event Sourcing
Normally, databases store the current state. (e.g., Account Balance = $50).
In Event Sourcing, the database stores the sequence of events that led to the state. (e.g., Deposited $100, Withdrew $50).
To get the current balance, you "replay" all the events from the beginning. (This is exactly how Git and accounting ledgers work).
Pros: Perfect audit trail. You can reconstruct the state of the system at any point in history. You can wipe the read database and completely rebuild it by replaying the event log.
CQRS (Command Query Responsibility Segregation)
In complex systems, the data model used for writing (Commands) is highly normalized, but the data model used for reading (Queries) needs to be heavily denormalized and fast.
CQRS physically separates the Write Database from the Read Database.
- Client sends a Command to the Write Service.
- Write Service saves to Write DB and emits an Event.
- Read Service hears the Event and updates the Read DB (e.g., an Elasticsearch index or a Redis cache).
- Client queries the Read Service.
(Note: CQRS introduces Eventual Consistency between the write and the read).
7. Implementation
Why Java? Java (specifically Spring Boot) is the undisputed king of Enterprise Event-Driven systems. The ecosystem around Kafka, event listeners, and domain-driven design is incredibly mature in Java.
/*
025 - Event-Driven Architecture
A Java simulation demonstrating Event Choreography.
An Order Service publishes an event, which is independently consumed
by an Inventory Service and a Shipping Service.
*/
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
// ── 1. The Core Event Model ──
class OrderPlacedEvent {
public final String orderId;
public final String item;
public final int quantity;
public OrderPlacedEvent(String orderId, String item, int quantity) {
this.orderId = orderId;
this.item = item;
this.quantity = quantity;
}
}
// ── 2. The Simple Event Bus (Mocking Kafka/RabbitMQ) ──
interface EventListener {
void onOrderPlaced(OrderPlacedEvent event);
}
class EventBus {
private final List<EventListener> listeners = new CopyOnWriteArrayList<>();
public void subscribe(EventListener listener) {
listeners.add(listener);
}
public void publish(OrderPlacedEvent event) {
System.out.println("\n[EventBus] 📢 BROADCASTING: OrderPlacedEvent(id=" + event.orderId + ")");
// In reality, this happens asynchronously over the network
for (EventListener listener : listeners) {
listener.onOrderPlaced(event);
}
}
}
// ── 3. The Microservices ──
class OrderService {
private final EventBus eventBus;
public OrderService(EventBus eventBus) {
this.eventBus = eventBus;
}
public void createOrder(String item, int quantity) {
String orderId = "ORD-" + System.currentTimeMillis();
System.out.println("[OrderService] 🛒 User requested checkout. Saving order to DB...");
// 1. Save to local DB (omitted)
// 2. Publish Event
OrderPlacedEvent event = new OrderPlacedEvent(orderId, item, quantity);
eventBus.publish(event);
System.out.println("[OrderService] ✅ Checkout complete. Returned HTTP 200 OK to User.");
}
}
class InventoryService implements EventListener {
@Override
public void onOrderPlaced(OrderPlacedEvent event) {
System.out.println(" -> [InventoryService] Received Event. Reserving " + event.quantity + "x '" + event.item + "'.");
}
}
class ShippingService implements EventListener {
@Override
public void onOrderPlaced(OrderPlacedEvent event) {
System.out.println(" -> [ShippingService] Received Event. Preparing shipping label for " + event.orderId + ".");
}
}
// ── 4. Test Harness ──
public class EDA_Simulation {
public static void main(String[] args) {
EventBus eventBus = new EventBus();
// Initialize Services
OrderService orderService = new OrderService(eventBus);
InventoryService inventoryService = new InventoryService();
ShippingService shippingService = new ShippingService();
// Wire up the Event Bus (Subscribers)
eventBus.subscribe(inventoryService);
eventBus.subscribe(shippingService);
System.out.println("--- System Initialized ---");
// The core business action
orderService.createOrder("MacBook Pro", 1);
// Notice how we didn't touch OrderService code to add a new feature!
System.out.println("\n--- Adding a new Marketing Service on the fly ---");
eventBus.subscribe(event -> {
System.out.println(" -> [MarketingService] Received Event. Sending 'Thanks for buying!' email.");
});
orderService.createOrder("AirPods", 2);
}
}
Sample Output
--- System Initialized ---
[OrderService] 🛒 User requested checkout. Saving order to DB...
[EventBus] 📢 BROADCASTING: OrderPlacedEvent(id=ORD-1712345678001)
-> [InventoryService] Received Event. Reserving 1x 'MacBook Pro'.
-> [ShippingService] Received Event. Preparing shipping label for ORD-1712345678001.
[OrderService] ✅ Checkout complete. Returned HTTP 200 OK to User.
--- Adding a new Marketing Service on the fly ---
[OrderService] 🛒 User requested checkout. Saving order to DB...
[EventBus] 📢 BROADCASTING: OrderPlacedEvent(id=ORD-1712345678099)
-> [InventoryService] Received Event. Reserving 2x 'AirPods'.
-> [ShippingService] Received Event. Preparing shipping label for ORD-1712345678099.
-> [MarketingService] Received Event. Sending 'Thanks for buying!' email.
[OrderService] ✅ Checkout complete. Returned HTTP 200 OK to User.
8. Complexity
| Concept | Architectural Complexity | Debugging Difficulty |
|---|---|---|
| Monolith | Low | Low |
| SOA (REST API) | Medium | Medium (Can trace HTTP calls) |
| Event-Driven (Choreography) | High | Very High (Requires Distributed Tracing, Block 034) |
9. Trade-offs
| Setup | Pros | Cons |
|---|---|---|
| Request-Driven | Easy to understand. Immediate consistency. Easy to handle errors (just return a 500). | Tight coupling. Cascading failures. Low resiliency. |
| Event-Driven | Maximum decoupling. High scalability. Services can be updated/deployed completely independently. | Eventual Consistency. Extremely hard to debug. What happens if the Inventory service fails but the Shipping service succeeds? |
10. Production Evolution
| Feature | This Implementation | Production |
|---|---|---|
| Event Broker | In-Memory List | Kafka or AWS EventBridge. |
| Delivery Guarantee | Fire & Forget | Outbox Pattern. If the DB saves the order but the network dies before publishing the event, data is inconsistent. The Outbox Pattern saves the Event to the exact same SQL database as the Order (in a single transaction). A background process reads the outbox table and pushes it to Kafka. |
| Schema Evolution | Hardcoded | Schema Registry. Over time, events change (e.g., adding discountCode). A Schema Registry (using Avro or Protobuf) ensures producers and consumers agree on the event format so updates don't crash old consumers. |
11. Common Bugs
| Bug | What happens | Fix |
|---|---|---|
| The Dual Write Problem | Service saves to DB, then publishes to Kafka. If Kafka is down, the DB is updated but the event is lost. System is permanently inconsistent. | Use the Transactional Outbox Pattern or Change Data Capture (Debezium). |
| Infinite Event Loops | Service A emits X. Service B hears X and emits Y. Service A hears Y and emits X. The system DDOS's itself instantly. | Very careful choreography design. Use correlation IDs to trace execution flow. |
| Duplicate Events | Kafka delivers a message twice. The shipping service ships the user two MacBooks. | Consumers must be Idempotent (Block 030). |
12. Interview Questions
-
What is the Outbox Pattern? Hint: Solves the dual-write problem. Instead of writing to the DB and then to the message broker, you write the business data and the event payload into two tables in the same DB using one ACID transaction. A background worker then publishes the event.
-
Explain Choreography vs Orchestration in Microservices. Hint: Choreography is decentralized (services react to events). Orchestration is centralized (a controller service commands other services). Choreography scales better but is harder to monitor.
-
What is CQRS? Hint: Command Query Responsibility Segregation. Splitting the read model from the write model. Used when complex reads (joins, full-text search) shouldn't impact the performance of heavy writes.
-
What is Event Sourcing? Hint: Storing the state of a system as a sequence of immutable events rather than overwriting the current state. Allows for perfect auditing and state reconstruction.
13. Used By (Downstream Blocks)
- 026 Distributed Transactions (Sagas) — Complex choreographed events often use the Saga pattern to handle rollbacks/compensating transactions when one step fails.
- 034 Distributed Tracing — Absolutely mandatory for monitoring Event-Driven systems.
14. Used In (Case Studies)
| System | Use Case |
|---|---|
| Uber | Massive Kafka-based EDA. A driver completing a trip emits an event that updates billing, rider history, driver ratings, and maps independently. |
| Netflix | Their recommendation engine and viewing history are entirely event-driven. |
| Kafka (Core) | Kafka itself is the underlying infrastructure that makes modern EDA possible. |
15. Related Blocks
| Relationship | Block |
|---|---|
| Previous | 024 Message Queues & Pub/Sub |
| Next | 026 Distributed Transactions |
16. Try It Yourself
Exercise 1: Compensating Transaction (Mini-Saga)
Modify the Java code so that if the InventoryService throws an OutOfStockException, the OrderService listens for an InventoryFailedEvent and marks the order status in its database as "CANCELLED".
Exercise 2: CQRS Implementation
Create two new classes: WriteDatabase (stores the raw orders) and ReadDatabase (stores a running total of items sold). When the OrderService places an order, a TotalSalesProjector listener should catch the event and update the ReadDatabase.
Website Metadata
| Field | Value |
|---|---|
| Hero Title | Event-Driven Architecture |
| Hero Subtitle | How to build infinitely scalable, decoupled systems that react in real-time. |
| Breadcrumb | System Design → Building Blocks → Event-Driven Architecture |
| Sidebar Category | Tier 3 — Distributed Systems |
| Search Keywords | event driven architecture, eda, microservices, kafka, event sourcing, cqrs, choreography, orchestration, outbox pattern |
| Internal Links | ← 024 Message Queues · → 026 Distributed Transactions |
| Suggested Illustration | A single megaphone (Producer) shouting into a busy city square, and three different people (Consumers) scattered around the square reacting independently to the news. |
| Suggested Animation | A request hits Service A, which tries to call B and C synchronously. C is broken, so A explodes. Switch to Event-Driven: A drops a message in a pipe, immediately turns green, and walks away. B and C read the pipe at their own pace. |