Event-Driven Architecture

Event-Driven Architecture (EDA) is a style where components communicate by publishing events and reacting to events rather than calling each other directly. A producer announces that something happened and doesn't know or care who consumes it. This decouples systems, scales workflows independently, and makes integrations resilient — at the cost of eventual consistency and the operational care that asynchronous systems demand.

The mental shift: events are facts about the past ("UserCreated"), broadcast to whoever's interested. That's powerful for fan-out and integration, but it's the wrong tool when you need an immediate, tightly-coupled request/response — use synchronous calls there.

TL;DR

Quick Example

A well-formed event carries identity, type, time, version, and payload:

Events vs Commands

The distinction matters: events describe what happened (past tense, no expectation of who acts); commands request that something happen (imperative, one owner).

Brokers & Delivery Semantics

Events flow through message queues, event streams, or pub/sub systems. Crucially, most real systems guarantee at-least-once delivery:

⚠️ Duplicates will happen. Consumers must be idempotent — processing the same event twice must be harmless.

See Message Queues.

Coordination Patterns

Event Design & Versioning

Include a unique id, type, timestamp, producer/version, and payload (see the Quick Example). Because events live a long time, evolve them in a backward-compatible way:

CQRS & Event Sourcing

💡 Many teams adopt "event-driven" for integration and decoupling without full event sourcing — you don't need one to get the other.

Reliability

Best Practices

Common Mistakes

Treating events like RPC

Missing idempotency

FAQ

When should I use events vs direct (synchronous) calls?

Use events when work can happen asynchronously, multiple consumers care, or you want to decouple producer and consumer lifecycles — fan-out, integration, background processing. Use synchronous calls when the caller needs an immediate result to proceed (e.g. "is this user authorized right now?"). Forcing a request/response interaction through events couples them anyway while adding latency and complexity.

Why must event consumers be idempotent?

Because nearly all brokers guarantee at-least-once (not exactly-once) delivery — network retries and redeliveries mean a consumer can receive the same event more than once. If processing isn't idempotent, duplicates cause double-charges, duplicate records, or corrupted state. Dedupe on the event id or a natural business key so reprocessing the same event has no additional effect.

What is the outbox pattern and why do I need it?

It solves the dual-write problem: you need to both update your database and publish an event, but doing them separately risks one succeeding and the other failing. The outbox pattern writes the state change and an "outbox" event row in a single transaction; a separate process then reads the outbox and publishes. This guarantees the event is published if and only if the data was committed.

Do I need event sourcing to do event-driven architecture?

No. Event-driven architecture is about communicating via events; event sourcing is a specific persistence strategy (store events, derive state). You can publish and react to events while still storing current state in a normal database — most event-driven systems do exactly that. Event sourcing adds auditability and temporal queries but also significant complexity, so adopt it only when those benefits are worth it.

Related Topics

References