Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Что такое API Composition
Определение
API Composition (Композиция API) — это архитектурный паттерн микросервисной архитектуры, который позволяет клиентам или промежуточному сервису получать данные из нескольких сервисов, объединяя их результаты в один ответ.
Это решение проблемы, когда данные, необходимые клиенту, распределены между несколькими микросервисами.
Проблема в микросервисной архитектуре
В традиционной монолитной архитектуре вся информация находится в одной базе данных:
// Монолит — всё в одном месте
public User getUserWithOrders(Long userId) {
User user = userRepository.findById(userId);
user.setOrders(orderRepository.findByUserId(userId));
return user;
}
В микросервисной архитектуре каждый сервис имеет свою БД:
- User Service — знает про пользователей
- Order Service — знает про заказы
- Payment Service — знает про платежи
Решение: API Composition
API Composition объединяет данные несколько сервисов на клиентской стороне или в специальном Composer Service (также называется Aggregator Service):
// Composer Service (Aggregator)
@RestController
@RequestMapping("/api/users")
public class UserComposerController {
private final UserServiceClient userClient;
private final OrderServiceClient orderClient;
private final PaymentServiceClient paymentClient;
public UserComposerController(
UserServiceClient userClient,
OrderServiceClient orderClient,
PaymentServiceClient paymentClient
) {
this.userClient = userClient;
this.orderClient = orderClient;
this.paymentClient = paymentClient;
}
@GetMapping("/{userId}")
public ResponseEntity<UserCompositeDTO> getUserWithAllData(@PathVariable Long userId) {
// Шаг 1: получить данные пользователя
UserDTO user = userClient.getUser(userId);
// Шаг 2: получить заказы этого пользователя
List<OrderDTO> orders = orderClient.getUserOrders(userId);
// Шаг 3: получить платежи для каждого заказа
List<PaymentDTO> payments = new ArrayList<>();
for (OrderDTO order : orders) {
payments.addAll(paymentClient.getOrderPayments(order.getId()));
}
// Шаг 4: композиция — объединить все данные
UserCompositeDTO composite = new UserCompositeDTO();
composite.setUser(user);
composite.setOrders(orders);
composite.setPayments(payments);
return ResponseEntity.ok(composite);
}
}
Архитектура API Composition
Клиент
|
v
Composer/Aggregator Service
|
+---> User Service API
+---> Order Service API
+---> Payment Service API
|
v
Ответ клиенту (объединённые данные)
Примеры реализации
Вариант 1: Синхронная композиция (блокирующие вызовы)
public UserCompositeDTO getUserSync(Long userId) {
// Вызовы идут последовательно (медленнее)
UserDTO user = userClient.getUser(userId); // wait
OrderDTO order = orderClient.getUserOrders(userId); // wait
PaymentDTO payment = paymentClient.getPayments(userId); // wait
return compose(user, order, payment);
}
Вариант 2: Асинхронная композиция (параллельные вызовы)
@Async
public CompletableFuture<UserDTO> getUserAsync(Long userId) {
return userClient.getUserAsync(userId);
}
@Async
public CompletableFuture<List<OrderDTO>> getOrdersAsync(Long userId) {
return orderClient.getOrdersAsync(userId);
}
public UserCompositeDTO getUserWithAsyncComposition(Long userId) {
CompletableFuture<UserDTO> userFuture = getUserAsync(userId);
CompletableFuture<List<OrderDTO>> ordersFuture = getOrdersAsync(userId);
// Все вызовы выполняются параллельно
CompletableFuture<UserCompositeDTO> composite =
userFuture.thenCombine(
ordersFuture,
(user, orders) -> new UserCompositeDTO(user, orders)
);
return composite.join();
}
Достоинства API Composition
- Простота реализации — нет необходимости в Event Bus или SAGA
- Отсутствие зависимостей между сервисами на уровне данных
- Контролируемая композиция — явно видно, какие данные откуда берутся
- Быстрое выполнение при асинхронной реализации
Недостатки API Composition
- Сетевая задержка — если вызывать сервисы последовательно, это медленнее
- Потребление памяти — всё объединяется в одном ответе
- Риск каскадных отказов — если один сервис недоступен, весь запрос падает
- Возможные несогласованности данных между вызовами
// Проблема: если Order Service недоступен, пользователь не получит ничего
public UserCompositeDTO getUserWithFallback(Long userId) {
try {
UserDTO user = userClient.getUser(userId);
List<OrderDTO> orders = orderClient.getUserOrders(userId);
UserCompositeDTO composite = new UserCompositeDTO();
composite.setUser(user);
composite.setOrders(orders);
return composite;
} catch (Exception e) {
// Fallback: вернуть хотя бы данные пользователя
return new UserCompositeDTO(userClient.getUser(userId), new ArrayList<>());
}
}
Сравнение подходов
| Подход | Сложность | Производительность | Надёжность |
|---|---|---|---|
| API Composition | Простая | Средняя | Зависит от сервисов |
| Event Sourcing | Сложная | Высокая | Вместимо-зависимая |
| SAGA Pattern | Очень сложная | Средняя | Хорошая |
Лучшие практики
- Используй асинхронность для параллельных вызовов
- Добавь timeout и retry logic для устойчивости
- Кэшируй результаты, если данные не меняются часто
- Используй fallbacks для graceful degradation
- Логируй и мониторь композицию для отладки
Заключение
API Composition — это мощный паттерн для объединения данных из нескольких микросервисов на клиентской стороне. Это простое, но эффективное решение, особенно когда используется асинхронная реализация. Однако важно учитывать сетевые задержки, обработку ошибок и кэширование для оптимальной производительности.