Every team that moves toward distributed services runs into this question at some point. Should this interaction be a direct request, or should it go through an event?

The debate usually gets framed as a technology choice. REST versus a message broker. Synchronous versus asynchronous. That framing misses the point. The pattern you choose changes who is responsible when things go wrong, how easy the system is to understand, and how much waiting your users can tolerate. It is a set of trade-offs, not a preference.

Start with the question you are actually answering

Request-response answers this question: “I need an answer right now, and I need to know if it failed.”

Events answer a different question: “Something happened, and other parts of the system may care, eventually.”

If you cannot tell which of those two sentences describes your interaction, you are not ready to pick a pattern yet.

The things that actually matter

1. Does the caller need the result to continue? If a user is waiting on a screen for a response, or the next step depends on the output, use request-response. Events are meant for “fire and move on.” Forcing a caller to wait on an event round trip usually means you have built request-response with extra steps and worse failure handling.

2. How many things care about this happening? One consumer, one clear owner, request-response is simple and fine. Multiple consumers that each need to react on their own (billing, notifications, analytics, audit logs) is exactly what events are good at. Trying to serve five consumers with five direct calls from the producer ties that producer to every consumer’s uptime and pace of change.

3. What does failure look like? With request-response, failure is immediate and visible. The caller gets an error and can retry or show a message to the user. With events, failure is often silent unless you build for it on purpose: dead-letter queues, monitoring on consumer lag, alerts on stuck messages. Teams that adopt events without this kind of monitoring are the ones who get paged three days later asking why something never happened.

4. How much delay can you tolerate? Request-response gives you the current state right away. Events introduce a gap, sometimes milliseconds, sometimes minutes, where the rest of the system has not caught up yet. If your logic cannot tolerate that gap (a balance check before something irreversible, for example), you need a direct, synchronous read, even inside an otherwise event-driven system.

The trap: choosing based on what is trendy

Event-driven design gets treated as the more scalable, more mature choice, and teams reach for it before they have actually run into the problems it solves. The cost is real. You trade instant consistency and easy debugging for flexibility you may not need yet.

A simple, synchronous system with three services and clear ownership is easier to run, easier to teach new engineers, and easier to debug at 2 a.m. than a five-service event setup with unclear consumer contracts, even if the event setup looks more advanced on a whiteboard.

The opposite trap exists too. Keeping everything synchronous because it is familiar, until a producer calls six downstream services directly, and every new addition makes deploys riskier and responses slower. That growing chain is the real signal to introduce an event.

A rule of thumb that holds up

Default to request-response until you can name the specific reason you need events: multiple independent consumers, a producer that should not know or care who is listening, or work where the caller genuinely does not need to wait.

Do not add events because that is how large companies do it. Add them when you can point to the actual coupling problem they solve for your system today, and be honest that you are also taking on the extra work of watching an asynchronous flow you can no longer trace in a single stack trace.

In practice, most systems need both

The healthiest systems are not purely one or the other. A checkout flow might use request-response right up until payment is confirmed, since the user needs to know it worked, and then move to events for everything after: send the receipt, update stock, notify the warehouse, log for analytics. None of those need to make the user wait, and none of them should share one point of failure with each other.

Make this choice for each interaction, not once for the whole system. The question is not “are we an event-driven team.” It is “does this specific interaction need an answer right now, or does it need to notify someone who can act on their own time.” Answer that honestly for each piece of your system, and the design mostly picks itself.