Home/Learn/Microservices/Synchronous vs Asynchronous Communication

Synchronous vs Asynchronous Communication

Intermediate
Communication

Sync calls couple availability (caller waits for response); async messaging decouples services via queues or topics, improving resilience but adding eventual consistency.

Overview

The choice between synchronous and asynchronous communication is one of the most consequential architectural decisions in microservices. **Synchronous** (HTTP/REST, gRPC): the caller waits for the response — simple to reason about, easy to debug, but creates **temporal coupling** (if the downstream service is slow or unavailable, the caller is also slow or fails). **Asynchronous** (Kafka, RabbitMQ, SQS): the producer publishes a message and moves on; the consumer processes at its own pace — decouples availability and enables independent scaling, but introduces **eventual consistency** and makes debugging harder (no direct stack trace across services). Most real systems use both: sync for queries and user-facing reads, async for commands and cross-service writes.

Synchronous Communication — Temporal Coupling

Sync HTTP calls with OpenFeign or WebClient are straightforward but cascade failures. If Service C is down, Service B fails, which makes Service A fail — a **cascade failure**. The depth of synchronous call chains determines blast radius. Mitigate with circuit breakers (Resilience4j), bulkheads, and timeouts. Prefer sync for: real-time queries, user-facing reads, and operations requiring an immediate response.

Microservices — synchronous HTTP with circuit breaker
// Synchronous: Order → Inventory → Warehouse call chain
// If Warehouse is down (or slow), the entire chain is affected
@Service
class OrderService {
    private final InventoryClient inventoryClient;  // HTTP Feign client

    @CircuitBreaker(name = "inventory")             // break the chain on failure
    @TimeLimiter(name = "inventory")                // enforce timeout
    public CompletableFuture<OrderResult> place(Order order) {
        // Blocks waiting for inventory response
        StockResponse stock = inventoryClient.reserve(order.getSku(), order.getQty());
        return CompletableFuture.completedFuture(new OrderResult(order, stock));
    }
}

// Use sync when:
// - User needs an immediate answer ("is item in stock?" → must be sync)
// - Business transaction requires a real-time response
// - Request volume is low-moderate
// - Services are co-located (low latency network)

Asynchronous Communication — Decoupling and Resilience

Async messaging (Kafka/RabbitMQ) breaks temporal coupling: the producer publishes and returns immediately; the consumer processes when ready. The broker acts as a buffer — even if the consumer is down for hours, messages queue up and are processed on restart. This enables **independent scaling** (scale consumers without touching the producer) and **shock absorption** (producer can continue at full speed during consumer bursts).

Microservices — async Kafka with fire-and-forget
// Asynchronous: Order publishes event → Inventory consumes independently
@Service
class OrderService {
    private final KafkaTemplate<String, OrderPlacedEvent> kafka;

    public Order place(CreateOrderRequest req) {
        Order order = orderRepo.save(new Order(req));
        // Fire-and-forget — does NOT wait for inventory to reserve
        kafka.send("order-placed", order.getId().toString(),
                   new OrderPlacedEvent(order.getId(), order.getItems()));
        return order;  // returns immediately regardless of inventory state
    }
}

@Component
class InventoryConsumer {
    @KafkaListener(topics = "order-placed")
    void reserve(OrderPlacedEvent event) {
        // Runs independently, can retry on failure, scales separately
        inventoryService.reserve(event.items());
    }
}

// Use async when:
// - Operations can be eventually consistent (email send, analytics update)
// - Consumers are slower than producers (need buffering)
// - Independent scaling of producer and consumer is required
// - High volume / throughput scenarios

Hybrid: Sync for Queries, Async for Commands

The practical guideline is: **sync for queries** (reads where the user waits for a response), **async for commands** (writes / state changes that can be processed eventually). This follows CQRS: query the read model synchronously; issue commands that flow asynchronously through the system. Saga choreography uses async for each step; an API Gateway response may be immediate with a "202 Accepted" and a `Location` header for polling.

Microservices — hybrid: 202 Accepted + async processing
// Practical hybrid architecture:
//
// READ  path (sync):  GET /orders/{id} → order-service → return Order DTO
// WRITE path (async): POST /orders → publish OrderCreated event → return 202 Accepted
//
// REST response for async command
@PostMapping("/orders")
ResponseEntity<?> placeOrder(@RequestBody @Valid CreateOrderRequest req) {
    String requestId = UUID.randomUUID().toString();
    orderCommandBus.send(new PlaceOrderCommand(requestId, req));  // async
    return ResponseEntity
        .accepted()
        .header("Location", "/orders/status/" + requestId)
        .body(Map.of("requestId", requestId, "status", "ACCEPTED"));
}

// Polling endpoint
@GetMapping("/orders/status/{requestId}")
OrderStatus getStatus(@PathVariable String requestId) {
    return orderStatusRepo.findByRequestId(requestId)
        .map(s -> new OrderStatus(s.getState(), s.getOrderId()))
        .orElse(new OrderStatus("PENDING", null));
}

Key Points to Remember

  • 1Sync: caller waits for response — simple, real-time, but creates temporal coupling + cascade failures
  • 2Async: producer publishes and moves on — decoupled, resilient, but eventually consistent
  • 3Sync call chains amplify failures — circuit breakers and bulkheads limit blast radius
  • 4Async messaging absorbs traffic bursts; broker buffers messages when consumer is down
  • 5Practical rule: sync for queries (user waits), async for commands (can be eventual)
  • 6202 Accepted + polling/webhook pattern bridges async processing with user-facing APIs

Interview Questions

Sign in to ask Aria
1

What is temporal coupling and how does async messaging eliminate it?

MediumThoughtWorks
2

When would you choose synchronous REST over Kafka for service-to-service communication?

MediumNetflix
3

How would you handle a user-facing POST that triggers an async pipeline — what do you return?

MediumAmazon
4

What are the consistency trade-offs of replacing a sync call chain with async messaging?

HardUber
5

How does an async message broker provide shock absorption for traffic spikes?

EasyDeliveroo

Ask Aria about Synchronous vs Asynchronous Communication

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…