Picture a checkout endpoint that, on paper, does one job: take the payment. In practice it calls inventory, then fraud scoring, then the loyalty ledger, then email, then the analytics pipeline — five synchronous HTTP hops, each one holding the customer's spinner hostage. On a quiet Tuesday the whole thing returns in 300ms. During a flash sale the loyalty service starts to crawl, and suddenly no one can buy anything. The card was charged. The order "failed."
That isn't a bug you can patch. It's the architecture telling you it was never built to run that way.
What request-response actually costs
Synchronous calls feel simple because they mirror how we think: ask a question, wait for the answer. The hidden cost is temporal coupling — every service in the chain has to be healthy, fast, and available at the exact same instant, or the caller eats the failure.
The math is unforgiving. Chain five services that each hold a respectable 99.9% uptime and your composite availability drops to roughly 99.5% — about 3.6 hours of downtime a month that no single team caused. Latency stacks the same way: p99 accumulates, and your "fast" endpoint is only as fast as its slowest dependency on its worst day. You've turned five independent systems into one fragile organism that fails together.
What "event-driven" actually means
The myth is that event-driven architecture means "put a queue in front of it." A queue is plumbing. The real shift is in what you send through it.
In request-response you issue commands: "charge this card," "deduct this stock." The caller owns the outcome and waits for it. In an event-driven system you publish facts: OrderPlaced, PaymentCaptured. The producer states what happened and moves on. It doesn't know — or care — who is listening. The loyalty service, the email service, and the analytics pipeline each subscribe and react on their own schedule.
Commands couple the sender to the receiver. Events invert it: the receiver depends on the sender, and the sender depends on no one.
That inversion is the whole game. A new consumer next quarter? It subscribes to the same event. No change to the producer, no new synchronous hop, no fresh way for checkout to break.
When async wins
Reach for events when you see these shapes:
- Fan-out. One thing happens and five unrelated systems need to know. Synchronous fan-out is five ways to fail; a published event is one.
- Spiky, uneven load. A queue is a shock absorber. It lets a burst of 10,000 orders drain into a consumer that comfortably handles 500 a second, instead of toppling it.
- Long-running work. Video encoding, report generation, KYC checks. Nobody should hold an HTTP connection open for ninety seconds.
- Team decoupling. When "add a feature" requires a meeting with three other teams, events let them build against your facts without booking your calendar.
None of this is free. You're trading immediate consistency for eventual consistency, and that trade has teeth. Consumers must be idempotent — the same event will be delivered twice, and you have to shrug it off. Debugging stops being a stack trace and becomes a distributed timeline. Ordering guarantees get subtle. If your team can't yet answer "what happens when this event arrives twice, out of order, an hour late?", async will hurt before it helps.
When request-response is still the right call
Async is a tool, not a religion. Keep it synchronous when the user is standing there waiting for a specific answer — a login, a balance check, a search result. Keep it synchronous for read-your-own-writes flows, where a user expects to see the row they just saved. And keep it synchronous for genuinely simple CRUD, where an event bus adds operational weight and buys you nothing. The failure mode I see most often isn't too little event-driven design — it's teams pushing a "user clicked save" command through Kafka and calling it architecture.
Where to start Monday
Don't rewrite the system. Find one painful synchronous fan-out — usually an order or a signup that triggers a cascade of side effects — and cut the non-essential consumers loose onto an event. Do the payment synchronously; publish OrderPlaced and let email, loyalty, and analytics react asynchronously. Reach for the outbox pattern so you never lose an event when the database commit succeeds but the broker call doesn't. One well-chosen seam will teach your team more about async than any diagram.
The question worth asking before your next endpoint: does the caller truly need this answer now, or have we just been making it wait out of habit?