Gymterview
senior

Как обеспечить обратную совместимость REST API?

Обратная совместимость (backward compatibility) — свойство API, при котором обновлённая версия продолжает корректно работать с существующими клиентами без необходимости их изменения.

Неломающие изменения (non-breaking)

  • Добавление нового поля в ответ — клиенты игнорируют неизвестные поля (tolerant reader).
  • Добавление нового endpoint-а.
  • Добавление нового опционального параметра запроса.
  • Добавление нового значения enum (в ответе).
  • Расширение допустимого диапазона значений.

Ломающие изменения (breaking)

  • Удаление или переименование поля из ответа.
  • Изменение типа поля ("age": 30 -> "age": "30").
  • Удаление endpoint-а.
  • Добавление обязательного параметра.
  • Изменение кода ответа.

Стратегии обеспечения совместимости

Стратегия Описание
Версионирование (URL/Header) /api/v1/users и /api/v2/users работают параллельно
Deprecation + Sunset (RFC 8594, 8977) Заголовки уведомляют о плановом удалении
Tolerant Reader (Закон Постела) Клиент игнорирует неизвестные поля, не зависит от порядка
API Evolution Постепенное развитие: добавление новых полей, deprecated для старых
Feature Flags Управление поведением через заголовки

Tolerant Reader Pattern

«Будь консервативен в том, что отправляешь, и либерален в том, что принимаешь.»

Пример
// Настройка Jackson для игнорирования неизвестных полей
@JsonIgnoreProperties(ignoreUnknown = true)
public record UserDto(Long id, String name, String email) {}

// Или глобально
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
Примеры реализации

Заголовки Deprecation и Sunset:

@GetMapping("/api/v1/users")
public ResponseEntity<List<UserV1Dto>> getUsersV1() {
    List<UserV1Dto> users = userService.findAllV1();
    return ResponseEntity.ok()
        .header("Deprecation", "true")
        .header("Sunset", "Sat, 01 Nov 2026 00:00:00 GMT")
        .header("Link", "</api/v2/users>; rel=\"successor-version\"")
        .body(users);
}

Паттерн эволюции API:

// Шаг 1: Добавляем новое поле, старое оставляем
public record UserDto(
    Long id,
    String name,
    String email,
    @Deprecated String userName,    // старое поле (deprecated)
    String displayName              // новое поле
) {
    @JsonProperty("userName")
    public String getUserName() {
        return displayName != null ? displayName : name;
    }
}

// Шаг 2: Через несколько релизов удаляем старое поле

Автоматическое обнаружение несовместимых изменений:

openapi-diff --fail-on-incompatible \
  api-spec-v1.yaml \
  api-spec-v2.yaml

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

  • Удаление полей без предварительного deprecated-периода.
  • Изменение типа поля без версионирования.
  • Добавление обязательного параметра в существующий endpoint.
  • Отсутствие автоматической проверки совместимости в CI/CD.
  • Бесконечная поддержка всех версий — вовремя удаляйте старые версии.

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

  • openapi-diff и Optic интегрируются в CI/CD для автоматической проверки совместимости.
  • API lifecycle management — платформы (Backstage, Kong) управляют жизненным циклом API.
  • Contract testing (Pact, Spring Cloud Contract) — стандарт для проверки совместимости.
  • Тренд на API Evolution вместо строгого версионирования.

На собеседовании: ключевое — разделить изменения на ломающие и неломающие, знать Tolerant Reader и заголовки Deprecation/Sunset. Частая ошибка — не упомянуть автоматическую проверку совместимости в CI/CD.