Event-driven architecture: a practical introduction 🌩️
EDA is not a silver bullet. It is more like a Swiss Army knife: very useful, but if you use it as a hammer, you will hurt yourself.
The Event-driven architecture (EDA) is a way of designing systems where components do not call each other directly (HTTP/gRPC in a chain), but communicate through events published on a broker (or event stream). Consumers react to events asynchronously.
What an event is (and what it is not) 🧠
An event is a fact that happened in the domain: something that already happened and cannot be undone by changing its name.
- Event:
OrderCreated,PaymentAuthorized,UserRegistered. - Not an event (usually):
UpdateUserTable,RecalculateReport(these are usually commands).
Events vs commands 🎯
The distinction seems trivial until you find yourself publishing events that are actually masked commands. The useful mental rule is: a command asks for an action and expects an outcome; an event tells you that something already happened and does not negotiate.
- Command: “do this” (intent, imperative, often expects a response).
- Event: “this happened” (notification, informational, can have many consumers).
In practice: commands are useful inside a bounded context; events are what you share between contexts, with a lot of discipline.
How to find events (without guessing) 🧩
If you are “inventing” event names in front of an IDE, you are already on the road to UserUpdated and its close relatives. A pragmatic approach is to start from business behavior: workshops such as EventStorming help surface facts (events), intentions (commands), and boundaries (aggregates/bounded contexts) with the right people at the table.
The three basic blocks: Producer → Broker → Consumer 🧱
flowchart LR
U[User] --> API[API / Application]
API -->|publishes event| B[(Broker / Stream)]
B --> C1[Consumer: Email]
B --> C2[Consumer: Analytics]
B --> C3[Consumer: Inventory]
- Producer: publishes events when something relevant happens.
- Broker / Event channel: receives, stores (depending on the technology), and distributes events.
- Consumer: reacts to events, independently and often in parallel.
Result: the user no longer waits for all the side effects (email, analytics, indexing, and so on) before receiving a response.
When EDA makes sense ✅
EDA shines when:
- a single action generates fan-out (many side effects);
- you want to decouple domains and let them evolve independently;
- you have variable loads and need a natural buffer;
- you accept (or want) eventual consistency.
Typical examples: notifications, data pipelines, fraud detection, search index updates, integrations between domains.
When not to use it (or at least: not right away) 🛑
EDA is fantastic for fan-out and decoupling, but it is not magic: it moves complexity from “synchronous” code to operations and contracts. If you do not have minimum basics today (logs, metrics, ownership), you risk paying compound interest.
- flows that require immediate response and strict end-to-end consistency;
- simple CRUD without real integrations;
- teams/organizations not ready to manage operations and governance.
EDA increases power and flexibility, but adds operational surface. If you do not have decent logging and tracing today, EDA will not “save” you — it will put you in a gym with weights tied to your ankles.
A quick decision framework 🧭
Use these questions as an initial filter:
- Does the user have to wait for everything to finish? If not → asynchronous is possible.
- Are there multiple potential consumers today or tomorrow? If yes → events help.
- Does the action produce independent side effects? If yes → natural fan-out.
- Are we ready for DLQ, retry, idempotency, observability? If not → risk.
Real trade-offs (the ones you discover in production) ⚖️
As long as you are in deck slides, EDA looks like only advantages. In production you quickly discover the bill: debugging is harder, eventual consistency must be explained to the business, and operational overhead increases. That is not a reason to avoid it; it is a reason to adopt it deliberately, with tools and processes before the traffic peak, not after.
Minimal glossary (to avoid arguing in meetings) 📚
Two minutes spent here often save two weeks of “but by topic you mean queue or stream?”. This is not a dictionary; it is just the minimum to speak the same language.
- Topic/stream: logical channel of events. It is not just a name: it defines scope, retention, and responsibility, which affects cost and governance.
- Consumer group: several consumers sharing the work (horizontal scalability). It defines how processing scales; choosing the partitioning key poorly leads to hotspots or ordering problems.
- DLQ: parking lot for problematic events. It is useful only if there is a process for triage and reprocessing; without a runbook, it becomes a forgotten landfill.
- Correlation ID: the red thread that links events and operations. It saves hours of investigation: if you do not propagate it, reconstructing an asynchronous flow becomes a puzzle.
Next steps 🚀
If this chapter felt “easy,” relax: the interesting part starts with contracts and schema evolution, delivery semantics, and governance.
- For communication and flows: see the pattern guide.
- For queue/stream/pub-sub choices: see the messaging guide.
- To avoid breaking contracts: see the event design guide.