Опирается на правила: R-VER-1..6 и R-VER-X1..X4 из REST API Style Guide → раздел Версионирование.

Важно знать

  • Версия в URL: /api/v1/orders. Формат — v + целое число.
  • Префикс /api обязателен для всех бизнес-эндпоинтов.
  • Новая версия только при breaking change. Non-breaking — в текущей.
  • Клиент обязан игнорировать неизвестные enum и поля в ответе.
  • Breaking: удалить/переименовать поле, изменить тип, удалить enum-значение, изменить HTTP-метод/URL.
  • Non-breaking: добавить optional поле, добавить enum value, добавить endpoint.
  • Минорная версия (v1.2) или дата (/api/2024) — запрещены.
  • Версия в query (?version=1) — запрещена.

REST API — это публичный контракт. Любое изменение, ломающее существующих клиентов, требует новой версии. UCP формулирует чёткую таблицу breaking vs non-breaking — каждое изменение классифицируется заранее, до merge.

Версия в URL-пути

R-VER-1..3:

/api/v1/orders               ✓
/api/v2/orders               ✓ (новая major-версия)

/api/v1.2/orders             ✗ — минорная
/api/2024/orders             ✗ — дата-версия
/api/v1.0/orders             ✗ — десятичная
/orders                      ✗ — без /api, без версии
/v1/orders                   ✗ — без /api

Префикс /api — обязательный namespace для бизнес-эндпоинтов. Служебные (/health, /metrics) — вне /api.

v1v2 — только при breaking change. Подробнее ниже.

Версия в query (/orders?version=1) — R-VER-X2 запрещено: ломает caching layer (одинаковый URL для разных версий), сложнее routing на прокси.

R-VER-5 — клиент обязан игнорировать неизвестные

R-VER-5: контракт клиента.

Клиент API ОБЯЗАН:

  • Игнорировать неизвестные значения enum (или явно обработать как unknown).
  • Игнорировать неизвестные поля в ответе.

На этом обязательстве основано R-VER-6: добавление нового enum-value или нового поля — non-breaking. Сервер развивает API без новой версии при условии, что клиенты следуют этому правилу.

Если клиент не следует правилу (например, использует strict JSON deserializer с failOnUnknownProperties: true) — он будет ломаться на каждом добавлении поля. Это проблема клиента, не API.

Spring RestClient + Jackson по умолчанию — игнорирует unknown fields. Это правильный default.

R-VER-6 — таблица изменений

Breaking (требуют новую версию)

ИзменениеПример
Удаление endpointудалили DELETE /orders/{id}
Удаление/переименование поляcustomerIduserId
Удаление/переименование query?customerId=?userId=
Удаление/переименование path-переменной/orders/{id}/orders/{orderUuid}
Обязательный новый параметр в запроседобавили required Idempotency-Key
Изменение типа поляstringnumber, objectarray
Изменение формата поляdatedate-time
Удаление значения из enumудалили OrderStatus.DRAFT
Изменение HTTP-методаPOST /ordersPUT /orders
Изменение URL-пути/orders/sales-orders
Изменение кода ответа200201
Изменение семантикиtotal теперь без налогов
Обязательный новый headerrequired X-Api-Key
Ужесточение валидацииmaxLength: 100maxLength: 50
Изменение Content-Typeapplication/jsonapplication/xml

Non-breaking

ИзменениеПример
Необязательное новое поле в ответедобавили metadata к OrderResponse
Необязательный новый query?status=...&channel=web
Новое значение в enumOrderStatus.RESERVED
Новый endpointPOST /orders/{id}/duplicate
Необязательный новый headerX-Request-Id (если уже не обязателен)
Ослабление валидацииmaxLength: 50maxLength: 100
Новый error codeORDER_LIMIT_EXCEEDED в ErrorCode enum
Изменение текста в detail или titleулучшили error message

R-VER-X4: новую версию не создаём для добавления необязательного поля — это non-breaking, добавляется в текущую версию.

Параллельная поддержка v1 и v2

Когда нужна breaking change:

  1. Создаём v2 с новым контрактом.
  2. v1 продолжает работать без изменений.
  3. Параллельная поддержка — обычно 6-12 месяцев.
  4. Deprecation для v1Sunset header, документация (см. Rate limiting и deprecation).
  5. Удаление v1 после миграции клиентов.

Spring:

@RestController
@RequestMapping("/api/v1/orders")
public class OrderControllerV1 { ... }

@RestController
@RequestMapping("/api/v2/orders")
public class OrderControllerV2 { ... }

Под капотом — общие use cases, разные DTO. v2-controller маппит новый формат на тот же CreateOrderCommand.

Альтернативы версионированию

«Header versioning» (Accept-Version: v1) — не используем. Причины:

  • Прозрачность — версия в URL видна в логах, dashboard, browser address bar.
  • Caching — proxy/CDN кешируют по URL, header-versioning ломает cache key.
  • Тестированиеcurl http://localhost/api/v1/orders явный, не требует header.

«Content negotiation» (application/vnd.example.v1+json) — индустриальный стандарт, но complex для большинства команд. UCP использует path-versioning.

Что запрещено

АнтипаттернПравилоЧто взамен
Минорная версия /api/v1.2/R-VER-X1целое число (v2)
Дата-версия /api/2024/R-VER-X1v1, v2
Версия в query ?version=1R-VER-X2в URL path
Endpoint без /apiR-VER-X3/api/v1/...
Endpoint без версииR-VER-X3v1 обязательно
Новая версия для optional поляR-VER-X4в текущей версии
Удаление поля без новой версииR-VER-6breaking → v2
Header versioning Accept-VersionR-VER-1path versioning
Параллельные v1/v2 без Sunset для v1R-DEP-*deprecation header

Куда дальше

  • REST API → Версионирование (нормативно) — формулировки.
  • URL и ресурсы — формат /api/v1/.
  • Rate limiting, файлы, deprecation — Sunset для v1.
  • Ошибки RFC 9457 — ErrorCode enum расширение non-breaking.
  • Kafka → event design — версионирование events (тот же принцип).
  • JSON и формат ответов — добавление optional полей.