CodeOath
← All posts
Architecture & Patterns70 min total · 17 parts

Microservices vs. Monolith: Architecture Patterns and Trade-offs

Contents — Part 5 of 17: Communication Patterns: Synchronous vs. Asynchronous
Part 5 of 17 · ~2 min

Communication Patterns: Synchronous vs. Asynchronous

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)
ExampleREST over HTTP, gRPCMessage queue (SQS, RabbitMQ), event stream (Kafka)
Caller waits for a responseYesNo — fires a message and moves on
CouplingCaller needs the callee to be up right nowCaller only needs the queue/broker to be up; the consumer can be down and catch up later
Failure modeCaller blocks or errors immediately if the callee is slow/downA 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.