Event-Driven Architecture

Build systems where components react to events instead of calling each other. Broker vs mediator topologies, the four kinds of event patterns, event design, and the real trade-offs.

Intermediate⏱ 6 min readLesson 9 of 13#architecture#event-driven#events#pub-sub#kafka

The big idea

At a wedding, the photographer doesn't phone the caterer, the band and the florist when the couple arrives. Someone announces "The couple has arrived!", and everyone who cares reacts in their own way: the band starts playing, the caterer serves drinks, the photographer takes pictures.

In an event-driven architecture (EDA), components announce facts ("OrderPlaced") instead of calling each other ("please reserve stock, then charge the card, then email the customer"). Whoever is interested reacts.

Event-driven: one producer publishes a fact; many consumers react independentlyEvent-driven: one producer publishes a fact; many consumers react independently

Request-driven vs event-driven

Drawing diagram…
Request-drivenEvent-driven
CouplingCaller knows every calleeProducer knows no consumers
Adding a new reactionChange the callerAdd a subscriber
If a consumer is downThe call failsEvents wait in the broker
Flow visibility✅ Easy to follow in code❌ Spread across services
ConsistencyImmediateEventual

Anatomy

PartRoleExamples
Event producerDetects something happened and publishes itOrders service
EventAn immutable fact, past tenseOrderPlaced { orderId, total, at }
Event channel / brokerDelivers events to consumersKafka, RabbitMQ, SNS/SQS, EventBridge, NATS
Event consumerReacts to events it's interested inInventory, Email, Analytics

Two topologies

Broker topology (choreography)

No central brain. Each service reacts to events and publishes its own.

Drawing diagram…

✅ Highly decoupled and scalable. ❌ Hard to see or change the overall flow; error handling is spread out.

Mediator topology (orchestration)

A mediator (orchestrator) receives the initial event and directs the steps.

Drawing diagram…

✅ The flow and error handling are in one place. ❌ The mediator is extra coupling and a potential bottleneck.

The four event patterns (Martin Fowler)

"Event-driven" means several different things. Knowing which one you mean avoids confusion:

PatternWhat the event carriesExampleTrade-off
1. Event notificationJust "something happened" + an ID{ type: "OrderPlaced", orderId: "o42" }; consumers call back for detailsTiny events, but consumers call the producer back (coupling returns)
2. Event-carried state transferThe data consumers need{ type: "CustomerMoved", customerId, newAddress }Consumers keep a local copy and never call back; data is duplicated
3. Event sourcingEvents are the source of truthThe account's state is the sum of Deposited / Withdrawn eventsFull history and replay; more complex
4. CQRSEvents keep separate read models up to dateWrite model publishes; read model projectsFast, tailored reads; eventual consistency
Drawing diagram…

Designing good events

{
  "eventId": "0b8c4f0e-6a57-4f7e-9b3d-2f1d1c9a7e11",
  "type": "shop.ordering.OrderPlaced",
  "version": 2,
  "occurredAt": "2026-09-28T10:15:00Z",
  "source": "ordering-service",
  "correlationId": "req-7f3c",
  "data": {
    "orderId": "o42",
    "customerId": "c7",
    "totalCents": 8997,
    "currency": "EUR",
    "lines": [{ "sku": "KB-1", "quantity": 1 }]
  }
}
GuidelineWhy
Past tense, business language (OrderPlaced, not CreateOrderRow)Events are facts, in the ubiquitous language
Unique eventIdConsumers deduplicate (idempotency)
version + a schemaEvolve safely; old consumers keep working
correlationIdTrace one business flow across services
Include what consumers need, but not your whole database rowAvoid callbacks without leaking internals
Standard envelopee.g. CloudEvents, a CNCF standard format

The hard parts (be honest)

ChallengeMitigation
Eventual consistency (the UI may show old data briefly)Design the UX for it: "Order received, confirming…"
Duplicate events (at-least-once delivery)Idempotent consumers: store processed event IDs
OrderingPartition by entity key (Kafka); include versions/timestamps
Lost events when saving + publishingTransactional outbox pattern
Hard to debug "what happened?"Correlation IDs, distributed tracing, an event catalog
Schema changes break consumersSchema registry, versioning, backward-compatible changes
Hidden coupling via event contentsTreat event schemas as public APIs; document them (AsyncAPI)

(The Kafka and Microservices topics cover delivery guarantees, the outbox, sagas and CQRS in depth.)

EDA inside a single application

Event-driven isn't only for microservices. Inside a modular monolith, modules can communicate with in-process domain events, keeping modules decoupled without any broker:

// A tiny in-process event bus
type Handler<E> = (event: E) => Promise<void>;
class EventBus {
  private handlers = new Map<string, Handler<any>[]>();
  on<E extends { type: string }>(type: E["type"], handler: Handler<E>) {
    this.handlers.set(type, [...(this.handlers.get(type) ?? []), handler]);
  }
  async publish(event: { type: string }) {
    await Promise.all((this.handlers.get(event.type) ?? []).map((h) => h(event)));
  }
}

bus.on("OrderPlaced", (e) => loyalty.awardPoints(e.customerId, e.totalCents)); // loyalty module
bus.on("OrderPlaced", (e) => email.sendConfirmation(e.orderId));               // notifications module

Later, if a module becomes a separate service, swap the in-process bus for Kafka or RabbitMQ; the handlers barely change.

When to use it

✅ Many independent reactions to the same business facts, spiky traffic that needs buffering, integrating many systems, real-time processing, audit trails.

❌ Simple request/response flows where the user needs an immediate answer, small systems where the added complexity isn't earned, and workflows needing strict, immediate consistency.

Key takeaways

  • Components publish facts and react to facts instead of calling each other directly.
  • Broker topology (choreography) maximises decoupling; mediator topology (orchestration) makes flows explicit.
  • Know which pattern you mean: notification, event-carried state transfer, event sourcing, or CQRS.
  • Design events as versioned public contracts with IDs and correlation IDs.
  • Plan for eventual consistency, duplicates, ordering and debugging from day one.