It's time to talk about how these autonomous scientist-microservices actually "talk" to each other. And, like in real life, there are two common ways: speak directly (synchronously) or send letters/messages (asynchronously).
Imagine a typical office workflow. When you urgently need info from a colleague, you go to them directly or call (synchronous communication). When the task isn't urgent, you send an email and keep doing other stuff while you wait for an answer (asynchronous communication). Which way you pick depends on how urgent and important the matter is.
Microservices can communicate in different ways depending on their responsibilities. For example:
- If microservice A needs an immediate response from microservice B, it calls it directly via REST API.
- If microservice A doesn't care about immediate response, it will send a message through a broker (for example, Kafka).
So, synchronous or asynchronous communication — these are two fundamental approaches that enable interaction between services in a microservice architecture.
Synchronous communication via REST API
Synchronous communication means a service makes a request and waits for a response from another service. It's like a phone call: you call a friend and wait for them to pick up. If they don't answer, you call again or hang up.
In the world of microservices, the most popular way for synchronous communication is REST API.
Example of synchronous interaction with REST
Say you're building a food ordering system. You have two microservices:
OrderService— responsible for handling orders.PaymentService— responsible for handling payments.
Scenario: when a customer places an order OrderService needs to call PaymentService to check if there are enough funds. Here's how that might look:
// OrderService: Calling PaymentService REST API (synchronous request)
@RestController
@RequestMapping("/orders")
public class OrderController {
@PostMapping
public ResponseEntity<String> createOrder(@RequestBody OrderRequest orderRequest) {
// Order creation logic
// Sending request to PaymentService
RestTemplate restTemplate = new RestTemplate();
String paymentUrl = "http://localhost:8081/payment/validate";
PaymentResponse paymentResponse = restTemplate.postForObject(paymentUrl, orderRequest, PaymentResponse.class);
if (paymentResponse.isPaymentValid()) {
return ResponseEntity.ok("Order created successfully.");
} else {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Payment failed.");
}
}
}
Advantages of synchronous communication
- Simple implementation: REST API is a standard many developers already know, and tools (for example, Spring RestTemplate and WebClient) make work a lot easier.
- Direct access: a service calls another service directly and gets an immediate response.
- Familiarity: REST API is widely used and understood by both developers and managers.
Limitations of synchronous communication
- Temporal coupling: if
PaymentServiceis unavailable, thenOrderServicealso "hangs", which can break the whole system. - Scaling issues: if load on
OrderServiceincreases,PaymentServicemust handle more requests too. - Increased latency: every REST call is waiting time, which can slow overall performance.
When to use synchronous communication?
- When an immediate response is required.
- When the dependency between services is tight (for example, payment validation).
- When failures in one service are critical for another.
Asynchronous communication using messages
Asynchronous communication means one service sends a message to another through a broker (for example, Kafka, RabbitMQ, or ActiveMQ) and doesn't wait for an immediate response. It's more like sending an email: you write the message and move on to other tasks, and the recipient will reply when they can.
Key concepts of asynchronous communication
- Message brokers: usually there's an intermediary like Kafka that stores and delivers messages.
- Producer and Consumer: the sending service is called a producer, and the receiving service is a consumer.
- Topics: messages are sent to "topics", which act like mailboxes.
Example of asynchronous interaction via Kafka
Continuing the example with OrderService and PaymentService. Suppose we want to send the order for payment processing asynchronously.
Publishing a message (Producer):
// OrderService: Sending a message to Kafka
@RestController
@RequestMapping("/orders")
public class OrderController {
private final KafkaTemplate<String, OrderRequest> kafkaTemplate;
public OrderController(KafkaTemplate<String, OrderRequest> kafkaTemplate) {
this.kafkaTemplate = kafkaTemplate;
}
@PostMapping
public ResponseEntity<String> createOrder(@RequestBody OrderRequest orderRequest) {
kafkaTemplate.send("payment-topic", orderRequest); // Send the message
return ResponseEntity.ok("Order created successfully. Payment will be processed.");
}
}
Processing the message (Consumer):
// PaymentService: Receiving a message from Kafka
@Component
public class PaymentConsumer {
@KafkaListener(topics = "payment-topic", groupId = "payment-group")
public void handlePayment(OrderRequest orderRequest) {
// Payment processing logic
System.out.println("Processing payment for order: " + orderRequest.getOrderId());
}
}
Advantages of asynchronous communication
- Reduced coupling: services operate independently. Even if
PaymentServiceis down,OrderServicekeeps working. - Better scalability: message brokers like Kafka can handle huge volumes of data.
- Flexible interaction: multiple consumers can subscribe to a single topic, which is handy for complex systems.
Limitations of asynchronous communication
- Implementation complexity: you need to set up a message broker and handle error cases (for example, undelivered messages).
- No immediate response: if the success of an operation is critical, this can be a problem.
- Delivery guarantees: not all message brokers provide strict delivery guarantees.
When to use asynchronous communication?
- When the task doesn't require immediate completion (for example, sending notifications).
- When you need to decouple services and reduce interdependencies.
- When you need to efficiently handle large volumes of data.
Choosing between synchronous and asynchronous communication
The choice depends on the specific scenario. Here are a few recommendations:
| Criterion | Synchronous (REST) | Asynchronous (Messages) |
|---|---|---|
| Immediacy of response | Required | Not required |
| Dependency between services | High | Low |
| Data volume | Small | Large |
| Scalability | Limited | High |
| Implementation complexity | Low | High |
Common mistakes when using the approaches
- Using REST for tasks that don't require an immediate response leads to increased latency and tighter coupling.
- Neglecting error handling in asynchronous communication can lead to lost messages.
- Poorly designed Kafka topics (for example, too many producers and consumers on one topic) creates chaos.
To avoid these problems, analyze system requirements carefully and pick the approach that best fits your goals.
At this point we've already figured out how microservices can interact with synchronous REST and asynchronous messages. In the next lecture we'll start digging into decomposing applications for microservice architecture. See you in the code! 😉
GO TO FULL VERSION