Events vs APIs vs Queues: When Should Your Microservices Use Each One?

Dynamic close-up of colorful neon lights reflected on a wet surface at night.

“Should this be an API call or should we queue it?” is a common question in design reviews. The less common but more important question is where events fit, because people quietly conflate them with queues — and that conflation is where a lot of production pain comes from. All three move data between services. They differ in who’s coupled to whom, whether there’s a caller waiting, and what happens when a consumer is slow or down. Get that mapping wrong and you either overcouple services that should be independent, or you lose delivery guarantees you assumed you had.

APIs: someone is waiting for an answer

An API call (REST or gRPC) is synchronous by nature — service A calls service B and blocks until it gets a response. That’s the right model exactly when a caller genuinely needs an answer now to proceed: checking inventory before confirming an order, validating a token before granting access, fetching a user’s profile to render a page.

The cost is temporal coupling: A’s availability now depends on B’s. If B is slow, A is slow; if B is down, A fails or needs its own retry/circuit-breaker logic. Fine for one or two hops — fragile fast when A calls B, which calls C, which calls D, and a single slow leaf node degrades everything above it.

Rule of thumb: use an API when there’s a caller blocking for a result, the two services are naturally allowed to be coupled (same team, same trust boundary or a controlled external one), and the call is a single, well-defined ask-and-answer.

Queues: hand off exactly one unit of work

A queue (SQS, RabbitMQ, a classic point-to-point broker) is for work distribution, not broadcast. A producer drops a message on the queue; exactly one consumer (from a pool of competing workers) picks it up, processes it, and removes it. This is the pattern for “this task must happen, exactly once, by whichever worker is free” — resizing an uploaded image, sending a single email, processing a payment.

Queues buy you load leveling and durability without fan-out. A burst of ten thousand image-resize jobs sits in the queue and gets worked through at whatever rate your consumer pool sustains, instead of falling over as ten thousand blocking calls. If a consumer crashes mid-processing, the message reappears and another worker picks it up — at-least-once delivery for free.

What queues don’t give you is multiple independent consumers reading the same message for different purposes. Once a worker takes a message off, it’s gone. Need a second, unrelated service to react to that same task too? You’ve outgrown the queue model.

Rule of thumb: use a queue when exactly one thing needs to process each unit of work, order doesn’t need to be strictly preserved across the whole system, and you want backpressure absorption more than fan-out.

Events: broadcast something happened, and move on

An event (Kafka, EventBridge, any pub/sub log) says “this occurred” and lets any number of independent consumers react, each at their own pace, without the producer knowing who they are or how many there are. This is the pattern when more than one service cares about the same fact: order.placed might need to trigger inventory decrement, a confirmation email, an analytics update, and a fraud check — four independent reactions to one fact, none of which should block the order service or each other.

The producer stays decoupled from every consumer’s existence. Add a fifth consumer next quarter and the order service’s code doesn’t change. A durable, replayable log (Kafka specifically) also lets a new consumer read historical events, not just future ones — useful for backfills or a new pipeline that needs six months of order history.

The trade-off is complexity: eventual consistency, harder debugging (no single call chain to trace — you’re following fan-out across consumer groups), and a real discipline requirement around schemas, since many consumers depend on the same event shape.

Rule of thumb: use an event when multiple independent services need to react to the same fact, none of them needs to block the producer, and you want the freedom to add new consumers without touching the producer.

Putting it together

APIQueueEvent
ModelSync request/responseAsync, one consumer per messageAsync, many independent consumers
CouplingCaller knows calleeProducer knows the queue, not the workerProducer knows nothing downstream
GuaranteeImmediate answer or failureAt-least-once, exactly one processorAt-least-once, every subscriber
Good for“I need this answer now”“This task must run once”“Multiple things care this happened”
Failure riskCascading latencyQueue backlogConsumer lag, schema drift

Where teams actually get burned

A common anti-pattern: an order service calling five downstream services synchronously (inventory, email, analytics, fraud, loyalty) because “it was simple to add each as an API call.” The endpoint’s p99 latency became the sum of its slowest dependency, and one flaky fraud-check took checkout down with it. The fix was publishing order.placed as an event and letting each reaction become an independent consumer — checkout latency dropped to just the order write, and a slow fraud service no longer took down checkout, it just ran a few seconds later.

The inverse also happens: teams put genuinely synchronous needs (a real-time check blocking an “add to cart” button) onto an event bus “for scalability,” then bolt correlation IDs and reply-topics on top to fake request/reply — recreating an API call, slower, with a new failure mode they didn’t have before.

The decision, in order

  1. Does a caller need an answer to proceed right now? Yes → API.
  2. Does exactly one worker need to do a specific task, once? Yes → Queue.
  3. Do multiple independent services need to know this happened? Yes → Event.

The pattern that causes outages isn’t picking the “wrong” one of these in isolation — it’s not noticing when a system’s shape has changed and the original choice no longer fits the number of things now depending on it.

Leave a Comment

Your email address will not be published. Required fields are marked *