How to organize communication between microservices
Microservices communicate in two main ways:
🟢 Junior Level
Microservices communicate in two main ways:
1. Synchronous (HTTP/gRPC):
Service A → HTTP request → Service B → HTTP response → Service A
2. Asynchronous (Kafka/RabbitMQ):
Service A → message to Queue → Service B picks it up when ready
🟡 Middle Level
Synchronous communication
REST:
@RestController
public class OrderController {
private final UserServiceClient userService;
@GetMapping("/orders/{id}")
public Order getOrder(@PathVariable Long id) {
Order order = orderRepository.findById(id);
User user = userService.getUser(order.userId());
return new Order(order, user);
}
}
gRPC:
service UserService {
rpc GetUser (UserRequest) returns (UserResponse);
}
Asynchronous communication
Kafka:
@KafkaListener(topics = "user-events")
public void onUserEvent(UserEvent event) {
if (event.type() == USER_CREATED) {
userRepository.save(event.toUser());
}
}
Common mistakes
- Cascading failures:
Service A → B → C → D D is down → C is down → B is down → A is down Solution: Circuit Breaker, Timeout
🔴 Senior Level
Architectural Trade-offs
When NOT to use synchronous communication
- Long call chains (A→B→C→D) — cascade failure risk
- Background tasks (sending email, generating reports)
- High load — asynchronous better buffers traffic spikes
| Synchronous | Asynchronous |
|---|---|
| Simpler: one HTTP call → response, no broker/serialization overhead. | More fault-tolerant |
| Faster for simple cases: no broker/serialization overhead. | Better for scaling |
| Temporal coupling | Decoupled |
| Cascading failures | Eventual consistency |
Production Experience
API Composition:
// thenCombine — combines two futures, waiting for both.
// join() — blocks until result (like get(), but without checked exception).
// If one future fails — the entire CompletableFuture pipeline fails.
@GetMapping("/dashboard/{userId}")
public CompletableFuture<Dashboard> getDashboard(@PathVariable Long userId) {
CompletableFuture<User> user = userService.getUserAsync(userId);
CompletableFuture<List<Order>> orders = orderService.getOrdersAsync(userId);
CompletableFuture<List<Notification>> notifications =
notificationService.getNotificationsAsync(userId);
return user.thenCombine(orders, (u, o) ->
new Dashboard(u, o, notifications.join()));
}
Best Practices
✅ Async for events
✅ Sync for data requests
✅ Circuit Breaker for sync calls
✅ Idempotency for async messages
❌ Long call chains
❌ Without timeout and retry
❌ Without error handling
🎯 Interview Cheat Sheet
Must know:
- Two approaches: synchronous (HTTP/gRPC) and asynchronous (Kafka/RabbitMQ)
- Synchronous: simpler, instant response, but cascade failure risk
- Asynchronous: more fault-tolerant, scalable, but eventual consistency
- API composition: combining results from multiple services (CompletableFuture.thenCombine)
- Circuit Breaker is mandatory for sync calls
- Idempotency is mandatory for async messages
- Do NOT use synchronous for long chains (A→B→C→D), background tasks, high load
Frequent follow-up questions:
- REST vs gRPC? REST is simpler, gRPC is faster (protobuf), stricter contract.
- Kafka vs RabbitMQ? Kafka — persistent log, replay, higher throughput. RabbitMQ — flexible routing, easier setup.
- How to avoid cascade failure? Circuit Breaker, Timeout, Retry — for sync; async queue — for background.
- What is API composition? Querying multiple services in parallel, combining results (thenCombine).
Red flags (NOT to say):
- “Synchronous is always better, it’s simpler” — no, cascade failure risk
- “Asynchronous for everything” — no, harder to debug, eventual consistency
- “Chain A→B→C→D→E is normal practice” — no, cascade failure is guaranteed
- “Idempotency is not needed for Kafka” — it is needed, duplicate delivery is possible
Related topics:
- [[16. What is the difference between synchronous and asynchronous communication]]
- [[5. What is Circuit Breaker pattern]]
- [[19. What is Retry pattern and how to use it correctly]]
- [[9. What is API Gateway and what problems does it solve]]
- [[2. What is the difference between choreography and orchestration in Saga]]