API Composition (API 합성) 패턴은 마이크로서비스 아키텍처(MSA)에서 클라이언트가 여러 마이크로서비스에 분산되어 있는 데이터를 한 번의 요청으로 통합하여 제공받을 수 있도록 해주는 패턴입니다.
이 패턴은 클라이언트가 여러 서비스를 직접 호출하는 오버헤드를 줄여주고, UI 복잡도를 낮추는 데 유용합니다. 일반적으로 API Gateway나 **전용 컴포지션 서비스(Composition Service)**에서 구현됩니다.
여기서는 주문(Order), 고객(Customer), 재고(Inventory) 서비스의 데이터를 통합하여 ‘주문 상세 정보’를 제공하는 API Composition Service의 Java (Spring Boot) 샘플을 작성해 드립니다.
💻 API Composition Pattern Java 샘플
이 예시는 Spring Boot의 RestTemplate (또는 WebClient 권장)을 사용하여 다른 마이크로서비스의 API를 호출하고 데이터를 합성하는 방식으로 구현합니다.
1. 데이터 모델 (DTOs)
다른 마이크로서비스에서 반환될 데이터 구조를 정의합니다.
// OrderDetail.java (최종적으로 클라이언트에게 제공할 합성된 데이터)
public class OrderDetail {
private String orderId;
private double totalAmount;
private String customerName; // CustomerService에서 합성
private List<ItemDetail> items; // InventoryService에서 합성
// Getters, Setters, Constructors, toString...
}
// ItemDetail.java (재고 정보 포함)
public class ItemDetail {
private String itemId;
private int quantity;
private String productName; // InventoryService에서 가져온 상세 정보
private double price;
// Getters, Setters, Constructors, toString...
}
// Order.java (OrderService에서 반환되는 기본 주문 정보)
public class Order {
private String orderId;
private String customerId;
private double totalAmount;
private List<String> itemIds; // 주문된 아이템 ID 목록
// Getters, Setters, Constructors, toString...
}
// Customer.java (CustomerService에서 반환되는 고객 정보)
public class Customer {
private String customerId;
private String name;
private String email;
// Getters, Setters, Constructors, toString...
}
2. Composition Service 구현
OrderCompositionService는 세 개의 하위 서비스를 호출하여 데이터를 통합하고 OrderDetail 객체를 반환합니다.
// OrderCompositionService.java
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class OrderCompositionService {
private final RestTemplate restTemplate;
// 실제 환경에서는 WebClient, Feign Client 등을 사용하여 비동기/리질리언스 처리 권장
public OrderCompositionService() {
this.restTemplate = new RestTemplate();
}
public OrderDetail getOrderDetail(String orderId) {
// 1. OrderService 호출: 기본 주문 정보 가져오기
System.out.println("[Composition] 1. OrderService 호출");
String orderUrl = "http://localhost:8081/api/orders/" + orderId; // OrderService 주소 가정
Order order = restTemplate.getForObject(orderUrl, Order.class);
if (order == null) {
return null;
}
// 2. CustomerService 호출: 고객 상세 정보 가져오기
System.out.println("[Composition] 2. CustomerService 호출: " + order.getCustomerId());
String customerUrl = "http://localhost:8082/api/customers/" + order.getCustomerId(); // CustomerService 주소 가정
Customer customer = restTemplate.getForObject(customerUrl, Customer.class);
// 3. InventoryService 호출 및 ItemDetail 합성
System.out.println("[Composition] 3. InventoryService 호출 및 ItemDetail 합성");
List<ItemDetail> itemDetails = order.getItemIds().stream()
.map(itemId -> {
// 각 아이템별 상세 정보를 InventoryService에서 가져옵니다.
// 실제로는 한 번의 호출로 여러 아이템 정보를 가져오는 배치 API를 사용하는 것이 효율적입니다.
String inventoryUrl = "http://localhost:8083/api/inventory/" + itemId; // InventoryService 주소 가정
ItemDetail itemData = restTemplate.getForObject(inventoryUrl, ItemDetail.class);
// 주문 Quantity 정보와 InventoryService의 상세 정보를 결합
// 예시에서는 ItemDetail DTO가 Quantity 필드를 이미 포함하고 있다고 가정
return itemData;
})
.collect(Collectors.toList());
// 4. 모든 데이터를 합성하여 최종 DTO 생성 및 반환
OrderDetail detail = new OrderDetail();
detail.setOrderId(order.getOrderId());
detail.setTotalAmount(order.getTotalAmount());
// 고객 이름 합성
detail.setCustomerName(customer != null ? customer.getName() : "Unknown Customer");
// 아이템 상세 목록 합성
detail.setItems(itemDetails);
System.out.println("[Composition] 4. 모든 데이터 합성 완료");
return detail;
}
}
3. Controller (진입점)

클라이언트의 요청을 받아 OrderCompositionService를 호출하는 API입니다.
// OrderCompositionController.java
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/order-details")
public class OrderCompositionController {
private final OrderCompositionService compositionService;
public OrderCompositionController(OrderCompositionService compositionService) {
this.compositionService = compositionService;
}
@GetMapping("/{orderId}")
public ResponseEntity<OrderDetail> getOrderDetails(@PathVariable String orderId) {
System.out.println("\n--- API Composition 요청 수신: " + orderId + " ---");
OrderDetail detail = compositionService.getOrderDetail(orderId);
if (detail != null) {
return ResponseEntity.ok(detail);
} else {
return ResponseEntity.notFound().build();
}
}
}
4. 시뮬레이션 (간단한 목업 데이터)
실제로 localhost:8081 등이 실행되지 않으므로, 테스트를 위해 간단한 목업(Mockup) 데이터를 사용하여 RestTemplate 호출을 시뮬레이션할 수 있습니다.
API Composition 패턴의 핵심은 단일 서비스가 여러 하위 서비스를 호출하여 데이터를 모으고 가공(합성)하는 책임을 지는 것입니다. 실제 MSA 환경에서는 **비동기 호출 (e.g., WebClient의 Mono/Flux)**을 사용하여 성능을 최적화하고, 회복 탄력성(Resilience, e.g., Hystrix/Resilience4j) 라이브러리를 적용하여 하위 서비스 실패에 대비해야 합니다.