SSourav Saha
HomeExperienceSoftware DesignSystem DesignLearningBooksToolsContact
SSourav Saha

Building scalable backend systems, distributed infrastructure, and cloud-native applications.

Navigation

  • Home
  • Experience
  • Software Design
  • System Design
  • Learning

More

  • Books
  • Tools
  • Contact

Connect

  • LinkedIn
  • Email

© 2026 Sourav Saha. All rights reserved.

Built with using Next.js

System DesignEvent-Driven Architecture
System Designadvanced

Event-Driven Architecture

Learn how to build highly decoupled, scalable systems using Event-Driven Architecture. Understand Event Sourcing, CQRS, choreography vs orchestration, and see a Java Spring Boot example.

April 20, 202412 min read
event-drivenarchitecturemicroserviceskafkacqrsevent-sourcingbuilding-blocktier-3

Metadata

FieldValue
Slugevent-driven-architecture
DifficultyAdvanced
Estimated Reading Time18 min
Estimated Coding Time25 min
Tier3 — Distributed Systems
Implementation LanguageJava
SEO DescriptionMaster 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 core Order Service code 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

SystemUse Case
UberDriver location updates, trip state changes, and pricing calculations are all real-time events flowing through Kafka.
NetflixViewing history and recommendations are updated asynchronously via events.
E-commerce CheckoutOrder placement triggers events for inventory, payment, shipping, and email services.
IoT SystemsMillions of sensors publishing telemetry events for downstream processing.

4. Prerequisites

ConceptBlock
Message Brokers024 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?

  1. 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.
  2. 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.

  1. Client sends a Command to the Write Service.
  2. Write Service saves to Write DB and emits an Event.
  3. Read Service hears the Event and updates the Read DB (e.g., an Elasticsearch index or a Redis cache).
  4. 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

ConceptArchitectural ComplexityDebugging Difficulty
MonolithLowLow
SOA (REST API)MediumMedium (Can trace HTTP calls)
Event-Driven (Choreography)HighVery High (Requires Distributed Tracing, Block 034)

9. Trade-offs

SetupProsCons
Request-DrivenEasy to understand. Immediate consistency. Easy to handle errors (just return a 500).Tight coupling. Cascading failures. Low resiliency.
Event-DrivenMaximum 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

FeatureThis ImplementationProduction
Event BrokerIn-Memory ListKafka or AWS EventBridge.
Delivery GuaranteeFire & ForgetOutbox 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 EvolutionHardcodedSchema 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

BugWhat happensFix
The Dual Write ProblemService 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 LoopsService 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 EventsKafka delivers a message twice. The shipping service ships the user two MacBooks.Consumers must be Idempotent (Block 030).

12. Interview Questions

  1. 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.

  2. 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.

  3. 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.

  4. 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)

SystemUse Case
UberMassive Kafka-based EDA. A driver completing a trip emits an event that updates billing, rider history, driver ratings, and maps independently.
NetflixTheir 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

RelationshipBlock
Previous024 Message Queues & Pub/Sub
Next026 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

FieldValue
Hero TitleEvent-Driven Architecture
Hero SubtitleHow to build infinitely scalable, decoupled systems that react in real-time.
BreadcrumbSystem Design → Building Blocks → Event-Driven Architecture
Sidebar CategoryTier 3 — Distributed Systems
Search Keywordsevent driven architecture, eda, microservices, kafka, event sourcing, cqrs, choreography, orchestration, outbox pattern
Internal Links← 024 Message Queues · → 026 Distributed Transactions
Suggested IllustrationA single megaphone (Producer) shouting into a busy city square, and three different people (Consumers) scattered around the square reacting independently to the news.
Suggested AnimationA 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.
PreviousLogging & Structured LogsNextRetry & Exponential Backoff