Reqflow
← All concepts
Distributed Systems·3 min read

Event-Driven Architecture

Services react to events others emit instead of calling each other directly: loose coupling at the cost of harder reasoning.

Try it

Emit one event and watch independent subscribers each react.

Order serviceemits "order placed"
Email service
Inventory
Analytics

Instead of the order service calling email, inventory, and analytics directly (and waiting for each), it emits one event. Subscribers react independently, and you can add a new one without touching the producer. The trade-off: flow is harder to follow and debug than a direct call chain.

First time reading this? Start here

Plain English: instead of Service A directly calling Services B, C, and D when something happens, A just announces 'this happened' (an event) and any service that cares listens and reacts. A doesn't even need to know B, C, and D exist. Very loosely coupled, but harder to follow the flow.

What it is

An architectural style where components communicate by producing and consuming events, which are immutable facts about something that happened ('OrderPlaced', 'PaymentCaptured'). Producers emit events to a broker without knowing who consumes them; consumers subscribe and react. Contrast with request-driven architecture, where a service explicitly calls the services it depends on.

The problem it solves

Direct request-driven calls tightly couple services: the caller must know every downstream, and adding a new reaction means changing the caller. Event-driven architecture inverts this: the producer announces a fact and stays ignorant of consumers. You can add new consumers (analytics, notifications, fraud) without touching the producer, absorb spikes through the broker, and let each consumer fail and recover independently.

How it works

A producer publishes an event to a broker (Kafka topic, message queue, event bus). Subscribers consume it asynchronously and react, often emitting their own events, forming a choreography. Two flavors: event notification (thin event, consumer fetches details if needed) and event-carried state transfer (event carries the full payload so consumers need no callback). The broker provides durability, replay, and buffering. Because everything is async, the system is eventually consistent rather than immediately consistent.

Why use it

  • Extreme loose coupling, since producers don't know or care who consumes their events
  • Add new consumers (analytics, notifications, audit) without touching producers
  • The broker buffers spikes and lets each consumer scale, fail, and recover independently

What it costs you

  • Eventual consistency by default: there's no single moment the whole system agrees on state
  • Hard to reason about and debug: the end-to-end flow is implicit, spread across many async reactions
  • At-least-once delivery means consumers must be idempotent, and event ordering across topics is not guaranteed

Where it shows up in our architectures

  • Apache Kafka

    The event backbone, where producers publish domain events to topics that any number of independent consumer groups react to

  • Notification System

    Other systems emit events ('order shipped'); the notification service reacts and fans out, with no caller coupling

  • Bluesky (AT Protocol)

    The global firehose Relay is an event stream; AppViews and custom feeds are consumers that react to the event log independently

Gotchas

  • Eventual consistency is the default, not a bug, but it surprises people. There's no instant after which the whole system is in sync; design UIs and flows to tolerate the lag.
  • Debugging is genuinely harder: no stack trace spans the flow. You need distributed tracing with event/correlation IDs propagated through every event, or incidents become archaeology.
  • Consumers must be idempotent (at-least-once delivery) and tolerant of out-of-order events across topics. Don't assume global ordering; partition by key where order matters.
  • Events are immutable facts in the past tense ('OrderPlaced'), not commands ('PlaceOrder'). Modeling them as commands re-couples producer and consumer and defeats the point.
Interview angle

Event-driven architecture comes up when you have multiple downstream services reacting to the same trigger. The key signal is knowing when NOT to use it: pure EDA makes the end-to-end flow implicit and hard to debug, so you want it for genuinely independent reactions (analytics, notifications, fraud) rather than for a core synchronous flow where you need a response. Always say your consumers must be idempotent, because at-least-once delivery means the same event will arrive more than once. Candidates lose points by proposing EDA for everything without addressing eventual consistency and observability across async hops.

Your notes

Private to you