Synchronous — the caller sends a request and blocks until it receives a response. Asynchronous — the caller sends a message and continues immediately without waiting for a response.
System Design Context: The choice between sync and async communication is fundamental to microservices architecture. Synchronous is simpler but creates tight coupling and latency chains. Async is more resilient but introduces eventual consistency and debugging complexity.
Synchronous (REST/gRPC) — Detailed:
1// Synchronous REST call — blocks the thread2@RestController3@RequestMapping("/api/orders")4public class OrderController {56 private final UserServiceClient userServiceClient;7 private final InventoryServiceClient inventoryClient;89 @GetMapping("/{id}")10 public OrderDTO getOrder(@PathVariable Long id) {11 Order order = orderService.findById(id);12 User user = userServiceClient.getUser(order.getUserId());13 // blocks for 50ms14 List<Item> items = inventoryClient.getItems(order.getItemIds());15 // blocks for another 100ms16 return OrderDTO.from(order, user, items);17 // Total: 150ms minimum18 }19}2021// Optimized with parallel calls (still synchronous)22@GetMapping("/{id}/optimized")23public CompletableFuture<OrderDTO> getOrderOptimized(@PathVariable Long id) {24 Order order = orderService.findById(id);2526 CompletableFuture<User> userFuture = CompletableFuture27 .supplyAsync(() -> userServiceClient.getUser(order.getUserId()));28 CompletableFuture<List<Item>> itemsFuture = CompletableFuture29 .supplyAsync(() -> inventoryClient.getItems(order.getItemIds()));3031 return userFuture.thenCombine(itemsFuture,32 (user, items) -> OrderDTO.from(order, user, items));33 // Total: ~100ms (max of the two parallel calls)34}
Asynchronous (Kafka/RabbitMQ) — Detailed:
1// Asynchronous event publishing2@Service3@Transactional4public class OrderService {56 private final OrderRepository orderRepo;7 private final KafkaTemplate<String, OrderEvent> kafkaTemplate;89 public Order createOrder(CreateOrderRequest request) {10 Order order = new Order(request);11 Order saved = orderRepo.save(order);1213 // Publish event — doesn't wait for notification service14 kafkaTemplate.send("order-events",15 new OrderCreatedEvent(saved.getId(), saved.getUserId(),16 saved.getTotal(), Instant.now()));1718 return saved;19 // Total: ~10ms (just DB save + async publish)20 }21}2223// Async consumer — processes events independently24@Service25@Slf4j26public class NotificationListener {2728 @KafkaListener(topics = "order-events",29 groupId = "notification-service")30 public void onOrderCreated(OrderCreatedEvent event) {31 log.info("Processing order event: {}", event.orderId());32 emailService.sendOrderConfirmation(event.userId(), event.orderId());33 inventoryService.reserve(event.orderId());34 }35}
Trade-offs Summary:
When to Use: