API Composition (API 합성) 패턴은 마이크로서비스 아키텍처(MSA)에서 클라이언트가 여러 마이크로서비스에 분산되어 있는 데이터를 한 번의 요청으로 통합하여 제공받을 수 있도록 해주는 패턴

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)

 

다른 마이크로서비스에서 반환될 데이터 구조를 정의합니다.

Java

// 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 객체를 반환합니다.

Java

// 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 (진입점)

API Composition (API 합성) 패턴은 마이크로서비스 아키텍처(MSA)에서 클라이언트가 여러 마이크로서비스에 분산되어 있는 데이터를 한 번의 요청으로 통합하여 제공받을 수 있도록 해주는 패턴

클라이언트의 요청을 받아 OrderCompositionService를 호출하는 API입니다.

Java

// 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., WebClientMono/Flux)**을 사용하여 성능을 최적화하고, 회복 탄력성(Resilience, e.g., Hystrix/Resilience4j) 라이브러리를 적용하여 하위 서비스 실패에 대비해야 합니다.