Event-Driven Architecture Introduction
What event-driven architecture is, events vs commands vs queries, and the benefits and costs of building systems around events.
What is event-driven architecture?
In an event-driven architecture (EDA), services communicate by publishing events — facts about things that already happened — instead of calling each other directly.
Request-driven: Orders ──call──▶ Payments ──call──▶ Email ──call──▶ Analytics
Event-driven: Orders ──publishes "OrderPlaced"──▶ [event broker] ──▶ Payments
├──▶ Email
└──▶ Analytics
The Orders service doesn't know or care who listens. New features — a loyalty-points service, a fraud check — subscribe to OrderPlaced without anyone changing the Orders code.
Events, commands and queries
| Message | Meaning | Naming | Example |
|---|---|---|---|
| Event | Something happened (a fact, past tense) | OrderPlaced, PaymentFailed |
"Order 1043 was placed at 10:02" |
| Command | A request to do something (may be rejected) | PlaceOrder, ChargeCard |
"Please charge 2,500 PKR" |
| Query | A request for data, no side effects | GetOrder |
"What's the status of order 1043?" |
Events are immutable — you can't un-happen something. If an order is cancelled, you publish OrderCancelled; you don't edit OrderPlaced.
Event notification vs event-carried state
- Event notification — a small event (
{"order_id": 1043}); consumers call back to fetch details. Small payloads, but creates coupling and extra load. - Event-carried state transfer — the event includes the data consumers need (
order_id, items, total, customer email). Consumers stay independent and keep their own copies.
Most modern systems lean towards event-carried state, carefully choosing which fields to include.
Benefits
- Loose coupling — producers and consumers evolve and deploy independently.
- Extensibility — add consumers without touching producers.
- Resilience — if the Email service is down, events wait in the broker; orders still work.
- Scalability — consumers scale horizontally and process at their own pace.
- Audit trail — a stream of events describes exactly what happened and when.
Costs (be honest about them)
- Eventual consistency — after an order is placed, the analytics dashboard might lag by seconds.
- Harder debugging — a request now flows through several asynchronous hops; you need correlation IDs and tracing.
- Duplicates and ordering — brokers usually deliver at least once, so consumers must handle duplicates and out-of-order events.
- Schema management — events are contracts; changing them carelessly breaks consumers.
- More infrastructure — a broker (Kafka, RabbitMQ, a cloud queue) to operate.
Building blocks you'll learn in this tutorial
| Concept | Solves | Lesson |
|---|---|---|
| Transactional outbox | Publishing events reliably alongside database writes | Outbox & Idempotency |
| Idempotent consumers | Processing duplicate deliveries safely | Outbox & Idempotency |
| CQRS | Separate, optimised models for writes and reads | CQRS |
| Event sourcing | Storing state as a log of events | Event Sourcing |
| Schema evolution | Changing events without breaking consumers | Event Schemas & Versioning |
For multi-step business processes across services (book seat → charge card → issue ticket), see the Saga Pattern lesson in the Microservices tutorial.
When to use EDA
Good fits:
- Several services must react to the same business event.
- Work can happen asynchronously (emails, notifications, analytics, search indexing).
- You need to absorb traffic spikes with a queue.
- You want an audit history of changes.
Poor fits:
- Simple CRUD apps with one database.
- Operations that need an immediate, consistent answer (checking a balance before a withdrawal).
Common mistakes
- Using events as remote procedure calls —
SendEmailToUseris a command, not an event. - Assuming exactly-once delivery — design for duplicates.
- Huge events with the whole database row "just in case" — every field becomes a contract.
- No correlation IDs — impossible to trace one customer action across services.
Key takeaways
- Events are immutable facts in the past tense; commands are requests.
- EDA decouples services and improves resilience at the cost of eventual consistency.
- Reliable publishing (outbox) and idempotent consumers are non-negotiable foundations.
- Use EDA where asynchronous reactions add value — not for every CRUD operation.