To facilitate communication between microservices, such as an Order Management system retrieving details from an Inventory system, client-side load balancing is essential. Spring Cloud Ribbon provides this capability seamlessly when integrated with Spring's RestTemplate.
Project Configuration
Begin by initializing the order management module. The critical step involves configuring the HTTP client bean. By applying the @LoadBalanced annotation, the RestTemplate becomes aware of the service discovery mechanism, allowing it to resolve service IDs to actual network locations.
package com.example.orders;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
public class OrderManagementApplication {
public static void main(String[] args) {
SpringApplication.run(OrderManagementApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate httpClient() {
return new RestTemplate();
}
}
API Controller Layer
Expoce an endpoint to trigger the order creation process. This controller accepts account and item identifiers, delegating the business logic to the service layer.
package com.example.orders.controller;
import com.example.orders.service.OrderProcessingService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/orders/v1")
public class OrderCreationController {
@Autowired
private OrderProcessingService processingService;
@PostMapping("/create")
public Object createOrder(
@RequestParam("account_id") int accountId,
@RequestParam("sku_id") int skuId) {
return processingService.processOrder(accountId, skuId);
}
}
Service Interface Definition
Define the contract for order processing. This abstraction allows different implementation strategies for remote service calls.
package com.example.orders.service;
import com.example.orders.domain.OrderRecord;
public interface OrderProcessingService {
OrderRecord processOrder(int accountId, int skuId);
}
Implementation Strategy 1: Direct Service ID
The simplest approach utilizes the configured LoadBalanced RestTemplate. You can directly reference the target service name (e.g., inventory-service) within the URL string. Ribbon intercepts this call to perform the load balancing.
package com.example.orders.service.impl;
import com.example.orders.domain.OrderRecord;
import com.example.orders.service.OrderProcessingService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.Date;
import java.util.Map;
import java.util.UUID;
@Service
public class OrderProcessingServiceImpl implements OrderProcessingService {
@Autowired
private RestTemplate httpClient;
@Override
public OrderRecord processOrder(int accountId, int skuId) {
String serviceUrl = "http://inventory-service/items/v1/query?sku=" + skuId;
Map<String, Object> itemDetails = httpClient.getForObject(serviceUrl, Map.class);
OrderRecord order = new OrderRecord();
order.setTimestamp(new Date());
order.setAccountId(accountId);
order.setTransactionId(UUID.randomUUID().toString());
order.setItemName(itemDetails.get("title").toString());
order.setAmount(Integer.parseInt(itemDetails.get("cost").toString()));
return order;
}
}
Implementation Strategy 2: Explicit Load Balancer Client
For scenarios requiring more control over instance selection, inject the LoadBalancerClient. This allows manual retrieval of the host and port before constructing request URL.
package com.example.orders.service.impl;
import com.example.orders.domain.OrderRecord;
import com.example.orders.service.OrderProcessingService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.Date;
import java.util.Map;
import java.util.UUID;
@Service
public class AdvancedOrderServiceImpl implements OrderProcessingService {
@Autowired
private RestTemplate httpClient;
@Autowired
private LoadBalancerClient balancer;
@Override
public OrderRecord processOrder(int accountId, int skuId) {
ServiceInstance instance = balancer.choose("inventory-service");
String baseUrl = String.format("http://%s:%d/items/v1/query?sku=%d",
instance.getHost(), instance.getPort(), skuId);
Map<String, Object> itemDetails = httpClient.getForObject(baseUrl, Map.class);
OrderRecord order = new OrderRecord();
order.setTimestamp(new Date());
order.setAccountId(accountId);
order.setTransactionId(UUID.randomUUID().toString());
order.setItemName(itemDetails.get("title").toString());
order.setAmount(Integer.parseInt(itemDetails.get("cost").toString()));
return order;
}
}