Event-Driven Architecture

📘 Chapter 8: Asynchronous Processing ⏱️ 10 min read 🏗️ Lesson 033

In event-driven architecture, services communicate by emitting and reacting to events rather than calling each other directly. Instead of "Service A calls Service B," it becomes "Service A announces something happened; whoever cares, reacts." This creates extreme loose coupling — but comes with tradeoffs.

Events vs Commands

A Crucial Distinction

  • Event (past tense, a fact): OrderPlaced, PaymentReceived, UserRegistered — something already happened. The publisher doesn't care who listens or what they do with it.
  • Command (imperative, an instruction): ProcessPayment, SendEmail, ReserveInventory — directed at a specific service, expecting it to do something.

Events are notifications of facts. Commands are requests to act. Event-driven architecture is built on events — services decide for themselves how to react.

The Event Bus

Event-Driven Architecture: Pub/Sub via Event Bus Event Bus / Broker Order Service OrderPlaced Payment Service PaymentProcessed Shipping Service OrderShipped Email Service Inventory Service Analytics Service Loyalty Service Publishers don't know who subscribes • Subscribers don't know who publishes • Adding a new subscriber requires zero changes to publishers
Figure 1: Services publish events to a central broker. Interested services subscribe. Neither side knows about the other.

Pub/Sub Pattern

Publish/Subscribe Decoupling

In pub/sub, the publisher emits events to a topic without knowing who (if anyone) subscribes. Subscribers register interest in topics without knowing who publishes.

  • Adding a new consumer requires zero changes to the publisher
  • Removing a subscriber doesn't affect anyone else
  • Multiple subscribers can react to the same event differently

This is why event-driven is so powerful for extensibility: to add audit logging, just subscribe a new service to existing events. No one else changes.

Event Schemas & Contracts

Why You Need a Schema Registry

When services communicate via events, the event structure becomes a contract. If the Order Service changes the OrderPlaced event schema (renames a field, removes data), all subscribers break.

  • Schema Registry: Central store of event schemas (e.g., Apache Avro, Protobuf, JSON Schema)
  • Backward compatibility: New versions must still work with old consumers
  • Forward compatibility: Old producers' events must work with new consumers
  • Versioning: OrderPlaced.v1, OrderPlaced.v2 — support both during migration

Choreography vs Orchestration

Choreography vs Orchestration Choreography (Decentralized) Order Payment Inventory Shipping Each service reacts to events and emits its own events. ✓ No single point of failure ✗ Hard to see the full flow Orchestration (Centralized) Orchestrator Payment Inventory Shipping Central coordinator directs each step in sequence. ✓ Easy to understand flow ✗ Orchestrator is a bottleneck
Figure 2: Choreography — services coordinate through events with no central controller. Orchestration — a central coordinator directs the workflow.

When to Use Which?

  • Choreography: Best for simple flows, few services, independent reactions. Each service owns its logic. Scales well but gets confusing with many services.
  • Orchestration: Best for complex workflows with ordering, compensation (saga pattern), and visibility requirements. Easier to monitor and debug, but the orchestrator becomes a critical path.
  • Hybrid: Many real systems use both — orchestration for critical business flows (order fulfillment) and choreography for cross-cutting concerns (analytics, notifications).

Advantages & Challenges

Why Event-Driven?

  • Loose coupling: Services don't know about each other — only about events
  • Easy extensibility: Add new consumers without changing existing services
  • Natural audit trail: Events form a history log of everything that happened
  • Independent scaling: Each consumer scales based on its own load
  • Failure isolation: One consumer being down doesn't affect others

The Challenges

  • Eventual consistency: Data across services is only eventually consistent — not immediate
  • Debugging distributed flows: "Why wasn't the email sent?" requires tracing across multiple services
  • Event ordering: Messages may arrive out of order; your logic must handle that
  • Distributed transactions: No simple ROLLBACK across services — requires saga patterns
  • Observability: You need correlation IDs, distributed tracing, and event flow visualization

🏢 Real-World: Netflix Content Ingestion Pipeline

When a new movie or show is uploaded to Netflix, it triggers an event-driven pipeline:

  1. VideoUploaded → Transcoding service encodes into 100+ formats (4K, HD, mobile, etc.)
  2. TranscodingComplete → Thumbnail service generates preview images
  3. ThumbnailsGenerated → Catalog service updates the content library
  4. CatalogUpdated → Recommendation engine re-indexes
  5. ContentReady → Notification service alerts subscribers who might be interested

Each service is independent — if the thumbnail service is slow, transcoding isn't affected. New services (like A/B testing thumbnails) can subscribe to existing events without changing anything upstream.

🏢 Real-World: E-Commerce Order Event Flow

An order flows through the system as a series of events:

  1. OrderPlaced → Payment service charges the card; Inventory service reserves stock
  2. PaymentProcessed → Email service sends receipt; Analytics records revenue
  3. InventoryReserved → Warehouse service creates pick list
  4. OrderShipped → Email sends tracking info; Loyalty service awards points
  5. OrderDelivered → Review service prompts for feedback; Analytics closes the loop

Notice: the Order service knows nothing about emails, loyalty points, or analytics. It just says "OrderPlaced" and walks away. This is the power of event-driven design.

Interactive: Build an Event Flow

Click services to add them to the event flow. Watch events propagate through the system:

Trigger event: