Concept · Architecture Patterns

Event-Driven Architecture

01

Why this matters

Order placed. Now: charge card, decrement inventory, send confirmation email, notify warehouse, update analytics, trigger fraud check, alert support if VIP. Six things must happen.

The naive "request-response" approach: the order service synchronously calls all six. Tightly coupled. Slow (sum of all six latencies). One fails → everything fails. Adding a seventh thing requires deploying the order service.

Event-driven architecture (EDA): the order service publishes one event ("OrderPlaced") and walks away. The six interested services subscribe, each reacts independently. Loose coupling. Async. New consumers added without touching the producer. Modern systems run on this pattern.

02

EDA vs request-response

Request-response

Producer commands consumer

"OrderService.placeOrder() calls PaymentService.charge() calls EmailService.send()". Producer knows about every consumer. Sequential. Each call must succeed or the chain fails.

Event-driven

Producer announces; consumers react

"OrderPlaced event published to a topic". Anyone subscribes. Producer doesn't know who's listening. Async by default. Each consumer fails independently.

03

The three EDA flavors

PatternWhat it addsUse when
Notification events"X happened" — payload is minimal, consumers query back for detailsMany consumers; payload is large or sensitive
Event-carried state transferEvent carries a snapshot of changed state — consumers don't need to query backConsumers need the data; want to reduce coupling further
Event sourcingEvents ARE the state — current state derived by replayAuditability, time-travel, financial systems
04

Choreography vs orchestration

How does a multi-step business process unfold? Two styles:

Choreography — each service listens to events and decides what to do. No central conductor. Order placed → Payment Service fires Charge → emits PaymentSucceeded → Inventory Service decrements → emits InventoryReserved → Shipping Service creates label.

Pros: services don't depend on each other knowing them. Easy to add a new step (just subscribe). Resilient — no single conductor to fail.

Cons: business flow is implicit, scattered across services. "Where's the order in this flow?" is hard to answer. Debugging requires distributed tracing.

Orchestration — a workflow service explicitly drives the steps. "Order Saga" calls Payment, then Inventory, then Shipping. Knows the sequence. Handles compensations.

Pros: business logic visible in one place. Easy to debug ("show me Order 42's saga state"). Compensation logic clear.

Cons: orchestrator becomes a complex service. Single point of coordination.

Rule of thumb

Choreography for ≤ 3 steps. Orchestration for > 5 steps or any with complex compensations. Tools like Temporal, AWS Step Functions, Camunda make orchestration tractable.

Transactional outbox pattern
-- Dual-write problem: write to DB + emit Kafka event atomically.
-- Solution: outbox table in same transaction as business write.

CREATE TABLE outbox (
  id            BIGSERIAL PRIMARY KEY,
  aggregate_id  BIGINT NOT NULL,
  event_type    TEXT NOT NULL,
  payload       JSONB NOT NULL,
  created_at    TIMESTAMPTZ DEFAULT now(),
  published     BOOLEAN DEFAULT false
);

-- Application writes both in one transaction:
BEGIN;
  INSERT INTO orders (...) VALUES (...);
  INSERT INTO outbox (aggregate_id, event_type, payload)
  VALUES ($orderId, 'OrderCreated', $payloadJson);
COMMIT;

-- Separate CDC process (Debezium) or polling worker reads new rows
-- from outbox, publishes to Kafka, marks published=true.
05

Deep dive — when EDA hurts

EDA is sold as universally good. It isn't. Real downsides:

Eventual consistency everywhere. Order placed at T=0; warehouse sees it at T+200ms. Customer immediately calls support: "where's my order?" Support service hasn't received the event yet. Real friction.

Debugging gets harder. A single user action generates events across 8 services. Tracing the causal chain requires distributed tracing (OpenTelemetry) and event correlation IDs. Painful without good tooling.

Schema evolution becomes a coordination problem. Add a field to OrderPlaced → 12 consumers may break. Need schema registry (Avro, Protobuf), strict backwards-compatibility rules.

Duplicate / out-of-order delivery. Most brokers are at-least-once. Consumers must be idempotent. Out-of-order events break naive state machines.

Hidden coupling reappears. "Producer doesn't know consumers" sounds great until two consumers each subtly depend on the producer's event timing or ordering. The coupling moves from APIs to event semantics — equally hard to change.

When EDA wins

You have ≥3 independent consumers of the same business event AND you control none of them OR they evolve at different rates. If you have 1 consumer, EDA is overkill — just call them.

06

Real-world

Uber Cadence / Temporal

Orchestration platform

Workflow-as-code. Used by Uber, Snap, Stripe for complex multi-step flows where saga + retries + compensations need to be explicit.

Shopify Pub/Sub

Event backbone

Order placed → ~50 internal consumers + external webhooks. Pure choreography. Built on Kafka.

AWS EventBridge

Managed event bus

Cross-service event routing with content-based filtering. SaaS apps publish events; AWS services + your code subscribe.

Stripe webhooks

External EDA

Stripe events the world by webhook. Customers' systems react. Effectively turns Stripe into an event publisher to a global event-driven ecosystem.

07

Used in problems

News feed uses events for fan-out (PostCreated → fan-out workers). E-commerce checkout is an orchestrated saga. Notification system is fundamentally a downstream consumer of events from every other service. Distributed job scheduler dispatches work via events.

Next up