Skip to content

Event-Driven Architecture

In a request-driven system, services call each other: “create this order,” “charge this card.” In an event-driven system, services announce what happened — “an order was placed” — and anyone interested reacts. This inversion is one of the most powerful (and most over-applied) ideas in system design. Understanding when it helps is the whole game.

The distinction is about intent and ownership:

COMMAND "ChargeCard" → directed at ONE service, expects it to act. Coupling: caller knows callee.
EVENT "OrderPlaced" → a fact, broadcast. Zero, one, or many consumers react. Caller knows no one.

A command says do this; an event says this happened. The shift from commands to events is what decouples the producer from its consumers — the order service doesn’t know (or care) that email, analytics, and inventory all react to OrderPlaced.

Producers publish to a log or broker (see Message Queues); consumers subscribe.

┌─────────────► Email service
Order service ──"OrderPlaced"──► [ event log ] ──► Inventory service
└─────────────► Analytics
(publishes once, knows nothing about who consumes)

Add a new consumer (say, a loyalty-points service) and you change nothing in the producer. That extensibility is the headline benefit.

Two flavours: choreography vs orchestration

Section titled “Two flavours: choreography vs orchestration”

When a business process spans services, you choose how it’s coordinated:

ChoreographyOrchestration
ControlEach service reacts to events independentlyA central coordinator directs each step
CouplingLoose; no one “owns” the flowTighter; the orchestrator knows the flow
VisibilityHard — the flow is emergentEasy — one place describes the whole saga
Best forSimple, stable reactionsComplex multi-step workflows needing rollback

Choreography is elegant until you need to answer “why didn’t this order ship?” and discover the logic is scattered across six services. Orchestration re-centralizes that at the cost of a coordinator.

Event sourcing stores the sequence of events as the source of truth instead of just current state. Current state is derived by replaying events.

Traditional: store balance = $80
Event-sourced: store [ +100 deposited, -20 withdrawn ] → replay → balance = $80

This buys a perfect audit log and the ability to reconstruct any past state — at the cost of more storage, replay complexity, and the need for snapshots so you don’t replay millions of events.

Here’s the failure that bites every event-driven system eventually. A service often needs to update its database AND publish an event. Those are two separate systems, and you cannot make them atomic:

1. write order to DB ✓
2. publish "OrderPlaced" ✗ (crash / broker down)
→ DB says order exists, but no one was told. State diverges.

You can’t fix this by reordering — publish first and the DB write might fail instead. The robust solution is the transactional outbox pattern, covered in The Dual-Write Problem & the Outbox Pattern. Flag it now: the moment you write to two systems, you have a consistency problem.

What does this buy us, and what does it cost? Events buy decoupling (add consumers freely), resilience (a down consumer doesn’t block the producer), and load absorption (the log buffers spikes). They cost eventual consistency, harder debugging, and the dual-write hazard. The core mental shift: model your domain as a stream of immutable facts, and let interested parties decide what to do with them.

  1. Distinguish a command from an event in terms of intent and coupling.
  2. Why can you add a new consumer to an event-driven system without touching the producer?
  3. Contrast choreography and orchestration; when is each appropriate?
  4. What does event sourcing store as the source of truth, and what problem do snapshots solve?
  5. Explain the dual-write problem and why reordering the two writes doesn’t fix it.