Choosing Event-Driven vs Request-Driven
Part 2 →
Event Modeling
Most teams reach for event-driven architecture because they heard it scales. What they often get instead is a distributed system where nothing talks to anything directly, debugging requires reading logs across five services, and a simple "get user profile" call somehow traverses a message broker. The pattern isn't wrong—the selection criteria are.
The real question is not "should we use events?" but "which interactions benefit from temporal decoupling, and which need synchronous answers right now?"
The Core Difference
Request-driven means caller A sends a message to receiver B and waits. The coupling is temporal—both must be available simultaneously. The contract is explicit: A knows what B returns, B knows who called it.
Event-driven flips that: producer P emits a fact into the world. Consumers subscribe to that fact and react. P doesn't know who listens or how many. The coupling is structural (schema) not temporal (uptime).
Decision Criteria
Neither style wins universally. Use these questions as a triage filter.
Ask: does the caller need the result immediately?
A login check, a price lookup, a payment authorization—these are synchronous by nature. The user is waiting. Putting a message on a queue and waiting for a reply event is just HTTP with extra steps and worse observability.
Ask: does the action trigger multiple downstream reactions?
An order is placed. You need to reduce inventory, send a confirmation email, update analytics, trigger fraud scoring, and notify the warehouse. Doing this synchronously means the order endpoint owns every one of those concerns. Add a new reaction later and you change the endpoint. Event-driven is the natural fit here—emit OrderPlaced, let each downstream service own its reaction.
Ask: can you tolerate eventual consistency?
Event-driven systems are inherently asynchronous. If your business rule says "the inventory count must be accurate before we confirm the order," you cannot publish an event and walk away. You need a synchronous check first, then an event for the downstream fanout.
Ask: what are your failure boundaries?
In a request chain, one slow service slows everything. In an event system, a slow consumer just falls behind—the producer is not blocked. If you have a slow-but-non-critical consumer (analytics, audit logs), events prevent it from degrading the critical path.
The Hybrid Reality
Production systems are neither purely event-driven nor purely request-driven. The pattern is layered:
The order service handles the synchronous user request: validate, charge, persist. Once that critical path succeeds, it emits an event. Everything downstream—inventory, notifications, analytics—consumes asynchronously. Users get fast responses. Side effects are decoupled.
Hybrid Patterns Worth Knowing
Request-Reply over events: the producer sends an event with a correlationId and blocks on a reply topic. Looks synchronous to the caller, but the underlying transport is async. Useful when you want location transparency (don't know where the processor runs) but need a response. The downside: timeout management is now your problem.
Choreography vs Orchestration: Choreography has services react to events independently. Orchestration has a central process manager drive the workflow via commands. Pure choreography scales well but makes the business process invisible—you can't look at one place and understand the flow. Orchestration makes the flow explicit at the cost of a central coordinator.
// Orchestration: explicit command
{
"type": "ProcessOrder",
"orderId": "ord-123",
"correlationId": "wf-456",
"step": "charge-payment"
}
// Choreography: fact broadcast
{
"type": "OrderValidated",
"orderId": "ord-123",
"timestamp": "2025-09-01T10:00:00Z"
}Saga pattern: a sequence of local transactions, each emitting an event that triggers the next step. Compensating transactions handle rollback. Use this when you need distributed transaction semantics without two-phase commit.
Common Misapplications
Teams often apply events in cases where they hurt more than help:
- CRUD wrappers: emitting
UserCreated,UserUpdated,UserDeletedfor every database operation. If nothing needs to react to those events, you've added a broker for no reason. - Event as RPC: emitting
GetUserRequestand waiting forGetUserResponse. This is REST over a message broker—slower, harder to debug, no benefit. - Shared mutable state via events: using events to propagate state changes to a shared database. You've kept the coupling, added latency, and introduced eventual consistency bugs.
Making the Call
| Scenario | Recommendation |
|---|---|
| User waits for result | Request-driven |
| One action triggers N reactions | Event-driven |
| Cross-service read | Request-driven |
| Long-running workflow | Saga / choreography |
| Non-critical side effects | Event-driven |
| Audit log, analytics | Event-driven |
| Authorization check | Request-driven |
Key Takeaways
- Temporal decoupling is the actual benefit of events—use it when callers don't need immediate answers.
- Fanout scenarios (one action, many reactions) are the clearest wins for event-driven design.
- Hybrid systems are the norm: synchronous on the critical user-facing path, async for downstream side effects.
- Request-reply over events looks sync but hides timeout complexity—use it intentionally.
- Choreography and orchestration are not mutually exclusive; choose based on whether process visibility matters more than decoupling.
- Avoid events for CRUD operations that have no real consumers—you are paying broker overhead for nothing.
Part 2 →
Event Modeling