Gymterview
senior

Как проектировать API для микросервисной архитектуры?

Проектирование API для микросервисов учитывает распределённую природу системы: сетевую ненадёжность, независимое развёртывание, согласованность данных и отказоустойчивость.

Синхронное взаимодействие (REST / gRPC)

Один сервис напрямую вызывает другой и ждёт ответа.

Пример вызова через RestClient
@Service
@RequiredArgsConstructor
public class OrderService {

    private final RestClient userServiceClient;

    public OrderDto createOrder(Long userId, CreateOrderRequest request) {
        UserDto user = userServiceClient.get()
            .uri("/api/users/{id}", userId)
            .retrieve()
            .body(UserDto.class);

        if (user == null) {
            throw new UserNotFoundException("User not found: " + userId);
        }

        Order order = orderMapper.toEntity(request);
        order.setUserId(userId);
        order.setUserEmail(user.email());

        return orderMapper.toDto(orderRepository.save(order));
    }
}

@Configuration
public class RestClientConfig {

    @Bean
    public RestClient userServiceClient(RestClient.Builder builder) {
        return builder
            .baseUrl("http://user-service:8080")
            .defaultHeader("Content-Type", "application/json")
            .requestInterceptor(new TokenRelayInterceptor())
            .build();
    }
}

Асинхронное взаимодействие (Events / Kafka)

Сервисы общаются через события — отправитель публикует событие, получатели подписаны на него.

Пример с Kafka
// Публикация события
@Service
@RequiredArgsConstructor
public class OrderService {

    private final KafkaTemplate<String, OrderEvent> kafkaTemplate;

    @Transactional
    public OrderDto createOrder(CreateOrderRequest request) {
        Order saved = orderRepository.save(orderMapper.toEntity(request));

        OrderEvent event = new OrderEvent(
            saved.getId(), "ORDER_CREATED",
            saved.getUserId(), saved.getTotal(), Instant.now()
        );
        kafkaTemplate.send("order-events", saved.getId().toString(), event);

        return orderMapper.toDto(saved);
    }
}

// Обработка события в другом сервисе
@Service
@Slf4j
public class OrderEventListener {

    @KafkaListener(topics = "order-events", groupId = "notification-service")
    public void handleOrderEvent(OrderEvent event) {
        if ("ORDER_CREATED".equals(event.eventType())) {
            notificationService.sendOrderConfirmation(event.userId(), event.orderId());
        }
    }
}

API Composition — агрегация данных из нескольких сервисов

Пример с Virtual Threads (Java 21+)
@RestController
@RequestMapping("/api/customer-view")
@RequiredArgsConstructor
public class CustomerViewController {

    private final RestClient userServiceClient;
    private final RestClient orderServiceClient;
    private final RestClient loyaltyServiceClient;

    @GetMapping("/{userId}")
    public ResponseEntity<CustomerView> getCustomerView(@PathVariable Long userId) {
        try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
            var userTask = scope.fork(() ->
                userServiceClient.get()
                    .uri("/api/users/{id}", userId)
                    .retrieve()
                    .body(UserDto.class));

            var ordersTask = scope.fork(() ->
                orderServiceClient.get()
                    .uri("/api/orders?userId={id}&limit=5", userId)
                    .retrieve()
                    .body(new ParameterizedTypeReference<List<OrderDto>>() {}));

            var loyaltyTask = scope.fork(() ->
                loyaltyServiceClient.get()
                    .uri("/api/loyalty/{id}", userId)
                    .retrieve()
                    .body(LoyaltyDto.class));

            scope.join().throwIfFailed();

            return ResponseEntity.ok(new CustomerView(
                userTask.get(), ordersTask.get(), loyaltyTask.get()
            ));
        }
    }
}

Circuit Breaker — защита от каскадных сбоев

Пример с Resilience4j
@Service
@RequiredArgsConstructor
public class UserServiceClient {

    private final RestClient restClient;

    @CircuitBreaker(name = "userService", fallbackMethod = "getUserFallback")
    @Retry(name = "userService")
    @TimeLimiter(name = "userService")
    public UserDto getUser(Long id) {
        return restClient.get()
            .uri("/api/users/{id}", id)
            .retrieve()
            .body(UserDto.class);
    }

    private UserDto getUserFallback(Long id, Throwable ex) {
        log.warn("User service unavailable, returning fallback for user {}", id, ex);
        return new UserDto(id, "Unknown User", null);
    }
}
resilience4j:
  circuitbreaker:
    instances:
      userService:
        sliding-window-size: 10
        failure-rate-threshold: 50
        wait-duration-in-open-state: 10s
  retry:
    instances:
      userService:
        max-attempts: 3
        wait-duration: 500ms
        exponential-backoff-multiplier: 2
  timelimiter:
    instances:
      userService:
        timeout-duration: 3s

Saga Pattern — распределённые транзакции

Saga — последовательность локальных транзакций, где каждый шаг имеет компенсирующее действие для отката.

Характеристика Хореография (события) Оркестрация (координатор)
Связанность Слабая (через события) Средняя (координатор знает все шаги)
Простота Простые Saga Сложные Saga с множеством шагов
Отслеживание Сложно (события распределены) Просто (всё в координаторе)
Единая точка отказа Нет Да (координатор)
Примеры Saga

Хореографическая Saga (через события):

@Service
@RequiredArgsConstructor
public class PaymentEventListener {

    private final PaymentService paymentService;
    private final KafkaTemplate<String, PaymentEvent> kafkaTemplate;

    @KafkaListener(topics = "order-events", groupId = "payment-service")
    public void handleOrderCreated(OrderEvent event) {
        if (!"ORDER_CREATED".equals(event.eventType())) return;

        try {
            paymentService.processPayment(event.orderId(), event.total());
            kafkaTemplate.send("payment-events",
                new PaymentEvent(event.orderId(), "PAYMENT_COMPLETED"));
        } catch (InsufficientFundsException e) {
            kafkaTemplate.send("payment-events",
                new PaymentEvent(event.orderId(), "PAYMENT_FAILED"));
        }
    }
}

Оркестрационная Saga:

@Service
@RequiredArgsConstructor
@Slf4j
public class OrderSagaOrchestrator {

    private final PaymentServiceClient paymentClient;
    private final StockServiceClient stockClient;
    private final DeliveryServiceClient deliveryClient;

    @Transactional
    public OrderResult executeOrderSaga(Order order) {
        try {
            StockReservation reservation = stockClient.reserve(order.getItems());

            PaymentResult payment;
            try {
                payment = paymentClient.charge(order.getUserId(), order.getTotal());
            } catch (Exception e) {
                stockClient.cancelReservation(reservation.getId());
                throw e;
            }

            try {
                deliveryClient.schedule(order.getId(), order.getDeliveryAddress());
            } catch (Exception e) {
                paymentClient.refund(payment.getId());
                stockClient.cancelReservation(reservation.getId());
                throw e;
            }

            order.setStatus(OrderStatus.CONFIRMED);
            return OrderResult.success(order);

        } catch (Exception e) {
            log.error("Order saga failed for order {}", order.getId(), e);
            order.setStatus(OrderStatus.FAILED);
            return OrderResult.failure(order, e.getMessage());
        }
    }
}

Ключевые принципы

  • Каждый микросервис владеет своими данными (database per service).
  • Синхронная связь (REST/gRPC) — для немедленного ответа. Асинхронная (Kafka) — для событий.
  • Circuit Breaker обязателен для всех синхронных вызовов.
  • Saga Pattern заменяет распределённые транзакции.
  • Все обработчики событий должны быть идемпотентными.

Частые ошибки

  • Общая база данных для нескольких сервисов — нарушает автономность.
  • Отсутствие Circuit Breaker — каскадный сбой всей системы.
  • Синхронные цепочки вызовов (A -> B -> C -> D) — увеличивают латентность.
  • Распределённые транзакции (2PC) — не масштабируются, используйте Saga.
  • Слишком мелкие сервисы (nano-services) — overhead превышает пользу.

Как используется в 2026

  • Spring Boot 3.x + Virtual Threads — синхронный код с производительностью реактивного.
  • Spring Modulith — модульный монолит как альтернатива для проектов среднего размера.
  • Testcontainers — стандарт для интеграционного тестирования.
  • OpenTelemetry — единый стандарт для distributed tracing, metrics и logging.
  • Service Mesh (Istio, Linkerd) берёт на себя retry, circuit breaker и mTLS.

На собеседовании: нужно показать знание обоих типов взаимодействия (синхронное и асинхронное), Circuit Breaker и Saga Pattern. Частая ошибка — не упомянуть принцип database per service или предлагать 2PC вместо Saga для распределённых транзакций.