Once services talk over a network instead of a function call, how they talk becomes an explicit design decision, not a given:
| Synchronous (request/response) | Asynchronous (messaging) | |
|---|---|---|
| Example | REST over HTTP, gRPC | Message queue (SQS, RabbitMQ), event stream (Kafka) |
| Caller waits for a response | Yes | No — fires a message and moves on |
| Coupling | Caller needs the callee to be up right now | Caller only needs the queue/broker to be up; the consumer can be down and catch up later |
| Failure mode | Caller blocks or errors immediately if the callee is slow/down | A backed-up queue, not an immediate caller-side failure |
| Natural fit for | "I need this answer before I can continue" (checking inventory before confirming an order) | "This happened, anyone who cares can react" (an order was placed — billing, shipping, and analytics all react independently) |
Synchronous calls are simpler to reason about — the caller gets an answer (or a clear failure) immediately — but they chain availability together: if service A calls B synchronously and B is slow, A is now slow too, and if B is down, A's request fails (or hangs, without a timeout — see resilience patterns below). Chain enough synchronous calls together (A calls B calls C calls D) and the whole request is only as reliable as the least reliable link, multiplied.
Asynchronous messaging decouples that chain — the producer publishes an event or drops a message on a queue and moves on, without needing the consumer to be available at that exact moment. This trades immediacy for resilience: the consumer can be temporarily down, slow, or scaled differently, and catches up when it's able to, rather than causing the producer to fail. The cost is complexity elsewhere — the producer generally can't get an immediate answer back this way (a genuinely synchronous need, like "is this credit card valid right now," usually still needs a synchronous call somewhere), and you inherit a new set of problems around message ordering, at-least-once vs. exactly-once delivery, and what happens to a message that a consumer repeatedly fails to process (typically routed to a dead-letter queue for later inspection rather than retried forever).
Most real microservice systems use both — synchronous calls for the specific, immediate lookups, and asynchronous events for propagating "something happened" to whoever else needs to react to it.