Event-driven architectures are often pitched as the modern, scalable way to build systems, but the truth is, they’re just a different way of managing state changes, and often, the simplest state change is a direct command.

Let’s see this in action. Imagine a simple e-commerce checkout.

Request-Response:

A user clicks "Buy Now."

  1. Client (Browser): Sends an HTTP POST request to /orders.
    • Payload: { userId: 123, productId: 456, quantity: 1, paymentToken: "tok_abc123" }
  2. Server (Order Service): Receives the request.
    • Validates the paymentToken.
    • Creates an Order record in the database.
    • Sends an HTTP 201 Created response back to the client.
    • Payload: { orderId: 789, status: "PENDING_PAYMENT" }
  3. Client: Receives the response and displays "Order Placed. Processing payment…"

This is synchronous. The client waits for a direct answer.

Event-Driven:

A user clicks "Buy Now."

  1. Client (Browser): Sends an HTTP POST request to /orders.
    • Payload: { userId: 123, productId: 456, quantity: 1, paymentToken: "tok_abc123" }
  2. Server (Order Service): Receives the request.
    • Validates the paymentToken.
    • Creates an Order record in the database with status INITIATED.
    • Publishes an OrderCreated event to a message broker (e.g., Kafka, RabbitMQ).
    • Sends an HTTP 202 Accepted response back to the client.
    • Payload: { orderId: 789, status: "INITIATED", message: "Order received. Processing..." }
  3. Client: Receives the response and displays "Order received. Processing…"

Now, other services react to the OrderCreated event:

  1. Payment Service: Subscribes to OrderCreated events.
    • Receives the OrderCreated event.
    • Processes the paymentToken.
    • Publishes a PaymentProcessed event (or PaymentFailed).
  2. Inventory Service: Subscribes to OrderCreated events.
    • Receives the OrderCreated event.
    • Decrements stock for productId: 456.
    • Publishes an InventoryUpdated event.
  3. Notification Service: Subscribes to PaymentProcessed and InventoryUpdated events.
    • Receives PaymentProcessed.
    • If successful, publishes an OrderShipped event.
    • Receives InventoryUpdated.
    • Sends an email to the user: "Your order is confirmed!"

The Problem Solved:

Request-response is great for simple, direct interactions where the client needs immediate feedback. It’s predictable and easy to debug for a single flow. Event-driven shines when you have multiple, independent services that need to react to state changes without direct coupling. It decouples services, allowing them to scale and evolve independently. If the Notification Service is down, the order can still be placed and paid for.

Internal Workings:

In request-response, the flow is a direct chain: Client -> Service A -> Service B -> Client. Each hop is a network call waiting for a response. In event-driven, it’s a broadcast model. A service emits an event, and any interested service can subscribe and react. This is often managed by a message broker, which acts as a central hub, reliably delivering messages (events) from publishers to subscribers. Services don’t need to know who will consume their events, only what event they are publishing.

The Levers You Control:

  • Event Schema: Defining the contract for your events. What data is included? How is it structured? This is crucial for compatibility between producers and consumers.
  • Message Broker Configuration: Throughput, durability, partitioning, replication, retention policies. These directly impact scalability, reliability, and cost.
  • Consumer Idempotency: Ensuring that a consumer can process the same event multiple times without causing unintended side effects. This is vital because message delivery guarantees are often "at-least-once."
  • Subscription Logic: Which services subscribe to which events? This defines the system’s behavior and data flow.

The most surprising true thing about event-driven systems is how much they can obscure the actual, physical state of a transaction. An OrderCreated event doesn’t guarantee that the order is fully processed; it just means it has been initiated and its creation has been recorded. The final state of the order is a composite of many subsequent events, and you often need to query multiple services or a dedicated "state aggregator" to get a complete picture.

The next concept you’ll grapple with is how to handle failures and ensure eventual consistency across these distributed, event-driven services.

Want structured learning?

Take the full Event-driven course →