14 May 2026 · 7 min read
Event-Driven Architecture, Minus the Hype
Everyone wants an event backbone until they meet eventual consistency at 2am. What event-driven actually buys you, what it costs, and a decision framework I use with real teams.
Every couple of years a client shows me a diagram with a big bus down the middle and the word “Kafka” written on it with visible pride. The diagram is usually beautiful. The question I ask is always the same: what invariant are you protecting, and who owns it?
Event-driven architecture is a genuinely great pattern. It is also the most over-prescribed medicine in enterprise IT. This post is the conversation I have with teams before we commit — with the diagrams I draw on the whiteboard, except these ones move.
What you’re actually buying
When you publish events instead of calling services directly, you’re buying temporal decoupling. The order service doesn’t need the email service to be awake. That’s it. That’s the product.
Look at the point-to-point picture. Four arrows out of orders. Every one of those arrows is a deployment dependency, a retry policy, a timeout budget, and a person the orders team has to talk to before they change anything. At four consumers it’s annoying. At twelve it’s organisational concrete.
Now the same domain with a broker in the middle:
The orders team now publishes one thing: a truthful, versioned record of what happened. Consumers come and go without a meeting. This is the good part, and it is real.
What you’re actually paying
Here’s the part the conference talk skipped. The moment state changes flow through an asynchronous pipe, you have signed up for:
- Eventual consistency as a product decision. “The stock number might be thirty seconds stale” is a sentence someone in the business has to approve, not something engineering quietly absorbs.
- Duplicate delivery. Every consumer becomes idempotent or every consumer is eventually wrong. There is no third option.
- Schema governance. An event schema is a public API with amnesia. Break it and you break consumers you’ve never heard of, replaying history you’ve forgotten you kept.
- A second operational surface. The broker itself: partitions, consumer lag, dead-letter queues, retention budgets. Someone owns that now.
// Idempotency isn't optional. This check is the tax
// every consumer pays, forever.
async function handleOrderPlaced(event: OrderPlaced) {
const seen = await processedEvents.exists(event.id);
if (seen) return; // duplicate delivery — broker at-least-once
await reserveStock(event.lines);
await processedEvents.record(event.id);
} If your team reads that and nods, good. If they ask why the broker would deliver the same event twice, you are not ready, and that’s fine — not ready is a place you can build from. Pretending is the expensive option.
The decision framework
When a team asks me “should this integration be an event?”, we score the honest answer to four questions. This is the aggregate from the last twenty-odd integration decisions I’ve refereed:
Read the last bar carefully. If the caller needs an answer — did the payment go through? — a request/response call is not a legacy pattern, it’s the correct one. The biggest event-driven failures I’ve seen were synchronous questions cosplaying as events, with a saga bolted on to simulate the answer.
The player-coach bit
I don’t hand this framework down from a slide deck. We run it as a workshop: the team brings three real integrations, we score them together, and I write the first consumer’s idempotency handling myself, in their codebase, in a pairing session. Then a mid-level engineer writes the second one while I watch and ask annoying questions.
Two integrations later they don’t need me for this decision any more. That’s the exit criteria for an architect. If your architecture only works while the architect is in the room, it isn’t architecture — it’s supervision.
Names and numbers in this post are composites from several engagements — the 2am pages were, regrettably, real.