A practical comparison of synchronous request-response interaction and asynchronous event-driven flow, with guidance on where each style fits.
Request-driven thinking starts from direct interaction: one caller asks one callee for an answer or action, and the flow is organized around that exchange. Event-driven thinking starts from change propagation: a system records a meaningful fact and allows other systems to react asynchronously. Strong architectures do not usually choose one forever and reject the other. They decide which parts of the system need immediate coordination and which parts benefit from looser asynchronous reaction.
This distinction matters because many weak designs are neither truly request-driven nor truly event-driven. They are synchronous dependencies disguised behind a queue. The transport changed, but the interaction model did not. To reason clearly, teams need to separate the question of “how do bytes move?” from “what dependency shape does the business flow require?”
sequenceDiagram
participant U as User
participant API as Checkout API
participant INV as Inventory
participant BUS as Event Bus
participant FUL as Fulfillment
U->>API: Submit checkout
API->>INV: Validate availability
INV-->>API: Availability result
API-->>U: Immediate confirmation
API->>BUS: Publish order.placed
BUS-->>FUL: React asynchronously
What to notice:
Request-driven systems optimize for direct interaction. The caller knows which service to contact and expects a useful answer in a relatively short time window. This model is often strong for:
Request-driven design makes dependency visible. That is useful. The drawback is that the caller becomes more tightly coupled to downstream availability, latency, and interface behavior.
Event-driven systems optimize for asynchronous propagation of meaningful change. A producer publishes a fact and does not need every consumer to answer before moving on. This model is often strong for:
The drawback is that the system now depends more on eventual consistency, retries, replay safety, and strong observability.
The strongest systems usually combine both approaches. A user-facing API often remains request-driven because the user needs confirmation, validation, or an immediate error. After that immediate step, events can carry the downstream consequences. This split is not a compromise. It is often the cleanest architecture.
A checkout, sign-up, or account-closure flow commonly works this way:
Treating the styles as complementary prevents a common mistake: replacing obvious APIs with asynchronous messaging even when the business flow still behaves synchronously.
1async function placeOrder(input: PlaceOrderInput) {
2 const inventoryOk = await inventoryClient.check(input.items);
3 if (!inventoryOk) {
4 throw new Error("inventory unavailable");
5 }
6
7 const order = await orderRepository.save(input);
8
9 await eventPublisher.publish({
10 eventName: "order.placed",
11 occurredAt: new Date().toISOString(),
12 data: {
13 orderId: order.id,
14 customerId: order.customerId,
15 totalAmount: order.totalAmount,
16 },
17 });
18
19 return { orderId: order.id };
20}
The code demonstrates a mixed model. Inventory validation remains synchronous because the order cannot proceed without it. Downstream fulfillment or analytics do not need to block the immediate response, so they are better represented through an event.
A flow is still request-driven when:
This is not a failure. It simply means the architecture should be evaluated with request-driven trade-offs in mind.
A flow fits event-driven thinking better when:
A team moves a synchronous approval workflow onto a broker but still requires the caller to wait for the approval result before continuing. Has the system become event-driven?
Not really in the way that matters architecturally. The system may now use messaging transport, but the business dependency remains request-like because the caller still needs a direct result to proceed. The design should therefore be evaluated as a messaging-based request/reply interaction, not as loosely coupled event fan-out.