Two Ways Services Talk to Each Other
In a microservices architecture, services must exchange data constantly, and the first design decision is whether that exchange happens synchronously or asynchronously. Synchronous communication means the caller sends a request and blocks, waiting for a response before it can continue; a typical HTTP call from an order service to an inventory service works this way. Asynchronous communication means the caller sends a message and moves on immediately, trusting that the response, if any, will arrive later through a callback, a queue, or a polling mechanism.
Cricket analogy: It's the difference between a batsman waiting at the crease for the umpire's decision on an lbw appeal before resuming play (synchronous) versus a fielder radioing a boundary alert to the dressing room and continuing to field without waiting for acknowledgment (asynchronous).
When Synchronous Calls Make Sense
Synchronous request-response, typically implemented over HTTP or gRPC, is the natural choice when the caller genuinely needs an immediate answer to proceed, such as a checkout service needing real-time confirmation that a credit card charge succeeded before it can show the customer an order confirmation. It is simpler to reason about because the control flow mirrors a normal function call, and errors surface immediately rather than being discovered minutes later. The cost is tight coupling in time: if the inventory service is slow or down, the caller is blocked or fails outright, and cascading latency can ripple across a whole call chain.
Cricket analogy: A third umpire review (DRS) is synchronous by necessity: play stops, everyone waits, and the match cannot continue until Hawk-Eye returns a verdict, just as a checkout flow must halt until payment authorization returns.
When Asynchronous Communication Wins
Asynchronous communication, typically implemented with message queues, event streams, or webhooks, is preferable when the caller does not need an immediate result, when the work can be retried safely, or when decoupling failure domains matters more than instant feedback. For example, sending a welcome email after signup, generating a PDF invoice, or updating a recommendation model can all happen after the fact without the user noticing a delay. This approach improves resilience because a downstream outage does not immediately propagate back to the caller; messages simply queue up and are processed once the consumer recovers.
Cricket analogy: A scorer updating the official records after the day's play, rather than mid-over, lets the match continue uninterrupted even if the scoring system briefly lags, just as a queued email doesn't block signup.
Mixing Both in Practice
Most real systems use a hybrid: synchronous calls for the critical path that the user is actively waiting on, and asynchronous flows for everything that can happen in the background. A common pattern is the synchronous request accepting the work and immediately returning a 202 Accepted with a tracking ID, while the actual processing happens asynchronously and the client polls or subscribes for the result. Choosing correctly per interaction, rather than picking one style globally, is what keeps a microservices system both responsive and resilient.
Cricket analogy: A captain sets the immediate field for the next ball synchronously but plans the bowling rotation for the rest of the innings asynchronously, blending both timescales in one strategy, like mixing 202-Accepted flows with background jobs.
// Synchronous-style call: caller blocks until the response arrives
async function placeOrder(cart) {
const paymentResult = await paymentService.charge(cart.total); // blocks
if (!paymentResult.success) throw new Error('Payment declined');
return { status: 'confirmed', orderId: paymentResult.orderId };
}
// Asynchronous-style call: caller fires and moves on
async function placeOrderAsync(cart) {
const jobId = await orderQueue.publish('order.created', { cart });
return { status: 'accepted', jobId }; // 202 Accepted, client polls later
}
// Background consumer processes the event independently
orderQueue.subscribe('order.created', async (event) => {
await inventoryService.reserveItems(event.cart.items);
await emailService.sendConfirmation(event.cart.customerEmail);
});Blocking synchronous chains across many services (A calls B calls C calls D) multiply latency and failure probability at every hop. A single slow downstream service can exhaust the caller's thread pool or connection pool, causing cascading failures across the whole system — this is why circuit breakers and timeouts are mandatory on every synchronous call.
A useful rule of thumb: use synchronous communication when the caller cannot make progress without the answer, and asynchronous communication for everything else, especially side effects like notifications, analytics, and background processing.
- Synchronous communication blocks the caller until a response arrives; asynchronous communication lets the caller continue immediately.
- HTTP request-response and gRPC unary calls are typically synchronous; message queues and event streams are typically asynchronous.
- Synchronous calls are simpler to reason about but create tight temporal coupling and can cascade failures across service chains.
- Asynchronous calls improve resilience and decoupling but add complexity around eventual consistency, retries, and tracking request state.
- A common hybrid pattern returns 202 Accepted synchronously while doing the real work asynchronously in the background.
- Timeouts and circuit breakers are essential safeguards on synchronous calls to prevent cascading failures.
- The choice should be made per interaction based on whether the caller genuinely needs an immediate result.
Practice what you learned
1. What is the defining characteristic of synchronous communication between services?
2. Which scenario is the best candidate for asynchronous communication?
3. What is a major risk of chaining many synchronous calls (A to B to C to D)?
4. What does returning a 202 Accepted response typically signal in a hybrid architecture?
5. Which mechanism is most commonly used to prevent cascading failures in synchronous call chains?
Was this page helpful?
You May Also Like
REST Between Services
Learn how RESTful HTTP APIs model resources, relationships, and versioning to provide a predictable, language-agnostic contract for synchronous microservice communication.
gRPC for Microservices
Explore how gRPC uses Protocol Buffers and HTTP/2 to deliver fast, strongly typed, streaming-capable communication between internal microservices.
Event-Driven Communication
Understand how publishing and subscribing to events decouples microservices in both time and knowledge, and the trade-offs between choreography and orchestration.
Message Brokers in Microservices
Learn how message brokers like RabbitMQ and Kafka durably deliver messages between services, the difference between queues and topics, and delivery guarantees.
Related Reading
Related Study Notes in Software Engineering
Browse all study notesTesting & TDD Study Notes
Software Testing · 30 topics
Software EngineeringDesign Patterns Study Notes
Software Design · 30 topics
Software EngineeringSoftware Engineering Study Notes
Python · 40 topics
Software EngineeringGit & Version Control Study Notes
Bash · 40 topics
Software EngineeringSystem Design Study Notes
Architecture · 40 topics