Gymterview
middle

Как версионировать API микросервисов?

Версионирование API необходимо для обратной совместимости — чтобы обновление одного сервиса не ломало потребителей его API. Три основных подхода: версия в URL, в заголовке и через Content-Type.

Подход Пример Плюсы Минусы
URL /api/v1/payments Простой, наглядный, кешируемый «Загрязняет» URL
Header X-API-Version: 1 Чистый URL Сложнее тестировать и документировать
Content-Type Accept: application/vnd.bank.v1+json Следует HTTP-стандартам Самый сложный
Примеры реализации всех подходов
// 1. Версия в URL (наиболее распространённый)
@RestController
@RequestMapping("/api/v1/payments")
public class PaymentControllerV1 {
    @GetMapping("/{id}")
    public PaymentResponseV1 getPayment(@PathVariable Long id) {
        return paymentService.getPaymentV1(id);
    }
}

@RestController
@RequestMapping("/api/v2/payments")
public class PaymentControllerV2 {
    @GetMapping("/{id}")
    public PaymentResponseV2 getPayment(@PathVariable Long id) {
        return paymentService.getPaymentV2(id);
    }
}

// 2. Версия в заголовке
@GetMapping(value = "/api/payments/{id}", headers = "X-API-Version=1")
public PaymentResponseV1 getPaymentV1(@PathVariable Long id) { ... }

@GetMapping(value = "/api/payments/{id}", headers = "X-API-Version=2")
public PaymentResponseV2 getPaymentV2(@PathVariable Long id) { ... }

// 3. Версия через Accept header (Content Negotiation)
@GetMapping(value = "/api/payments/{id}",
            produces = "application/vnd.bank.payment.v1+json")
public PaymentResponseV1 getPaymentV1(@PathVariable Long id) { ... }

Правила эволюции API (обратная совместимость)

  • Добавление нового поля в ответ — безопасно (клиенты игнорируют неизвестные поля).
  • Удаление обязательного поля — НЕ безопасно, требует новой версии.
  • Изменение типа поля — НЕ безопасно.
  • Добавление нового опционального параметра запроса — безопасно.
  • Используйте @JsonIgnoreProperties(ignoreUnknown = true) на DTO-клиентах для толерантности к новым полям.

Стратегия deprecation

Пример
@Deprecated(since = "2025-01-01", forRemoval = true)
@GetMapping("/api/v1/payments/{id}")
public PaymentResponseV1 getPaymentV1(@PathVariable Long id) {
    response.addHeader("Sunset", "Sat, 01 Jul 2025 00:00:00 GMT");
    response.addHeader("Deprecation", "true");
    response.addHeader("Link", "</api/v2/payments>; rel=\"successor-version\"");
    return paymentService.getPaymentV1(id);
}

На собеседовании: рекомендуйте версионирование через URL как самый простой и распространённый подход. Обязательно упомяните правила обратной совместимости и Sunset-заголовок для deprecated API.