Опирается на правила:
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.
v1 → v2 — только при 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} |
| Удаление/переименование поля | customerId → userId |
| Удаление/переименование query | ?customerId= → ?userId= |
| Удаление/переименование path-переменной | /orders/{id} → /orders/{orderUuid} |
| Обязательный новый параметр в запросе | добавили required Idempotency-Key |
| Изменение типа поля | string → number, object → array |
| Изменение формата поля | date → date-time |
| Удаление значения из enum | удалили OrderStatus.DRAFT |
| Изменение HTTP-метода | POST /orders → PUT /orders |
| Изменение URL-пути | /orders → /sales-orders |
| Изменение кода ответа | 200 → 201 |
| Изменение семантики | total теперь без налогов |
| Обязательный новый header | required X-Api-Key |
| Ужесточение валидации | maxLength: 100 → maxLength: 50 |
Изменение Content-Type | application/json → application/xml |
Non-breaking
| Изменение | Пример |
|---|---|
| Необязательное новое поле в ответе | добавили metadata к OrderResponse |
| Необязательный новый query | ?status=...&channel=web |
| Новое значение в enum | OrderStatus.RESERVED |
| Новый endpoint | POST /orders/{id}/duplicate |
| Необязательный новый header | X-Request-Id (если уже не обязателен) |
| Ослабление валидации | maxLength: 50 → maxLength: 100 |
| Новый error code | ORDER_LIMIT_EXCEEDED в ErrorCode enum |
Изменение текста в detail или title | улучшили error message |
R-VER-X4: новую версию не создаём для добавления необязательного поля — это non-breaking, добавляется в текущую версию.
Параллельная поддержка v1 и v2
Когда нужна breaking change:
- Создаём
v2с новым контрактом. v1продолжает работать без изменений.- Параллельная поддержка — обычно 6-12 месяцев.
- Deprecation для
v1—Sunsetheader, документация (см. Rate limiting и deprecation). - Удаление
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-X1 | v1, v2 |
Версия в query ?version=1 | R-VER-X2 | в URL path |
Endpoint без /api | R-VER-X3 | /api/v1/... |
| Endpoint без версии | R-VER-X3 | v1 обязательно |
| Новая версия для optional поля | R-VER-X4 | в текущей версии |
| Удаление поля без новой версии | R-VER-6 | breaking → v2 |
Header versioning Accept-Version | R-VER-1 | path versioning |
Параллельные v1/v2 без Sunset для v1 | R-DEP-* | deprecation header |
Куда дальше
- REST API → Версионирование (нормативно) — формулировки.
- URL и ресурсы — формат
/api/v1/. - Rate limiting, файлы, deprecation —
Sunsetдля v1. - Ошибки RFC 9457 —
ErrorCodeenum расширение non-breaking. - Kafka → event design — версионирование events (тот же принцип).
- JSON и формат ответов — добавление optional полей.