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.