Стратегии версионирования API
Зачем нужно версионирование
API меняется быстрее, чем клиенты. Версионирование позволяет выпускать фичи и править ошибки без поломок интеграций, задает ритуал изменений и делает эволюцию предсказуемой. Ключ: аддитивные изменения по умолчанию, majors — только для ломок, автоматические проверки совместимости и продуманная политика sunset.
Базовые принципы
1. Additive-first: новые опциональные поля/методы/события — ок; удаления и изменение смысла — мажор.
2. MGC (минимальный гарантийный контракт) стабилен; обогащения — через capabilities/`?include=`.
3. Tolerant reader: клиенты игнорируют неизвестные поля и корректно переживают расширения enum.
4. Semantic Versioning: `MAJOR.MINOR.PATCH` для артефактов, SDK и событий.
5. Automate: schema-diff, линтеры и CDC-тесты блокируют breaking-изменения.
Где хранить версию (механики адресации)
REST/HTTP
URI-версия: `/v1/orders` → просто маршрутизировать, удобно в шлюзах. Минус — «заслоняет» эволюцию представлений.
Медиатип/заголовок: `Accept: application/vnd.example.orders.v1+json` — точное управление форматом; сложнее дебажить.
Query-версия: `?api-version=1` — удобно для A/B, но легко потерять в кешах/логах.
Рекомендация: URI для major-линий + feature/capability flags и контент-негацияция для минорных расширений.
gRPC / Protobuf
Пакеты/сервисы: `package payments.v1; service PaymentsV1;` — явные линии.
Нумерация тегов неизменна; удаленные теги не переиспользовать.
Новые поля — `optional`; breaking → новый `.v2`.
GraphQL
Схема без явных major в URL. Эволюция через @deprecated и окна миграции; «настоящий» major — новая эндпоинт-схема.
Контролируйте complexity/depth — это часть контракта.
Event-driven (Kafka/NATS/Pulsar)
Имя события: `payment.authorized.v1` — версия в типе.
Реестр схем (Avro/JSON Schema/Protobuf) с режимами совместимости (BACKWARD/FORWARD/FULL).
Major → dual-emit `v1` и `v2` на переходный период.
Модель версий и политика изменений
Semantic Versioning
MAJOR — ломающие изменения: удаление/переименование полей, смена ключей партиционирования, иной смысл статусов, ужесточение валидации.
MINOR — аддитивные: новые необязательные поля/эндпоинты/события, новые enum-значения при tolerant-reader.
PATCH — исправления без изменения контракта и семантики.
Deprecation & Sunset
Помечайте устаревшее (`Deprecated: true`, `@deprecated`), публикуйте sunset-дату, альтернативу и гайд миграции.
В HTTP — заголовки `Sunset`, `Deprecation`; в событиях — отдельное `.deprecation.notice`.
Ведите телеметрию usage для принятия решения о снятии.
Версионные стратегии по стилям
REST
Major-линии на /v1, /v2.
MINOR: расширение схем, `?fields=`, `?include=`; безопасные enum-расширения.
ETag/If-Match и идемпотентные POST для согласованности без ломок.
Ошибки — стабильный формат (`type`, `code`, `trace_id`).
gRPC
Вводите новые сервисы/методы для ломок: `PaymentsV2.Capture`.
Статусы ошибок и семантика deadline — часть контракта; изменение → новый метод/сервис.
Стриминг: договоритесь о порядке сообщений и не меняйте его в minor.
GraphQL
Добавляйте поля и типы свободно; удаления — через `@deprecated` + окно миграции; большой редизайн → новая схема (`/graphql-v2`).
Интроспекция и описания — must-have для миграций клиентов.
Events
Core vs enriched: ядро стабильно, обогащения живут рядом и версионируются отдельно.
Ключ партиционирования неизменен в пределах major-линии.
Major-миграции — `dual-emit` + миграция проекций/консьюмеров.
Negotiation и capability-флаги
Capabilities: клиент отправляет `X-Capabilities: risk_score,price_v2`; сервер отвечает расширенным представлением.
Prefer (HTTP) и «hints» для частичных ответов.
В сокетах/стримах — handshake-сообщение со списком поддерживаемых расширений.
Выпуск major-версий без боли
1. RFC/ADR: почему нужен major, что ломается, матрица рисков.
2. Dual-run: параллельный запуск `v1` и `v2` (двойная публикация событий, два gateway-роута, зеркалирование трафика).
3. Миграционные адаптеры: прокси/трансляторы `v1↔v2` для тяжелых клиентов.
4. Канарейка: по группам клиентов/топикам/тегам в gateway.
5. Sunset-план: даты, коммуникация, стенды, тестовые данные, мониторинг использования.
Роли платформы и инфраструктуры
API Gateway/Service Mesh: маршрутизация по версии, заголовкам, путям; rate-limit и auth per-version.
Schema Registry & Catalog: источник истины для спек/схем с историей диффов.
CI/CD: линтеры (Spectral, Buf), schema-diff (OpenAPI-diff, Buf breaking), CDC (Pact).
Observability: версия должна попадать в логи/трейсы/метрики.
Тестирование версий
Schema-diff в PR: блокировать breaking.
Consumer-Driven Contracts: провайдер проверяется против контрактов реальных потребителей.
Golden samples: эталонные ответы на версии.
E2E-канарейка: сравнение p95/p99, ошибок и таймаутов между версиями.
Replay для событий: проекции пересобираются на v2 без расхождений.
Миграция данных и баз
Для REST/gRPC: миграции БД координируются через expand-and-contract (добавь колонку → начни писать → переключи чтение → удали старое).
Для Events: dual-emit и миграция консьюмеров; иногда — репроигрывание лога на новые проекции.
Не делайте «больших взрывов» — дробите на шаги с откатами.
Взаимосвязь с безопасностью
Scopes per version: отдельные OIDC-scopes/роли для v1/v2.
Секреты/claim’ы токена — часть контракта; их смена = major.
PII/PCI — не расширяйте payload без надобности; новые поля — с минимумом привилегий.
Антипаттерны
Swagger-wash: спецификация обновлена, сервер — нет (или наоборот).
Переиспользование protobuf-тегов — тихая порча данных.
Смена enum-смыслов без major.
Глобальное `/v2` «ради косметики»: версия без факта ломки.
Забыли sunset/usage-метрики: невозможно снять старую версию безопасно.
Один общий топик для разных major: смешение порядков и ключей.
Чек-лист перед релизом
- Изменения аддитивны или подготовлена отдельная major-линия.
- Линтеры и schema-diff зеленые (breaking не пролез).
- Обновлены SDK/примеров/документации, сноски про совместимость.
- Включен tolerant reader в клиентах; enum-fallback протестирован.
- Для major — план dual-run, адаптеры, канарейка, sunset-даты и рассылка.
- Метрики/логи/трейсы содержат версию и сегментацию по ней.
- Есть стенд и golden-наборы для сравнения v1↔v2.
- Для событий — реестр схем в режиме BACKWARD/FULL, ключи партиционирования неизменны.
Примеры шаблонов
REST (URI + negotiation)
Маршрут: `/api/v2/orders/{id}`
Заголовок: `Prefer: include=items,customer`, `X-Capabilities: risk_score`
Deprecation: `Sunset: 2026-06-30`, `Link: ; rel="alternate"`
Protobuf/gRPC
proto package payments.v2;
service PaymentsV2 {
rpc Capture (CaptureRequestV2) returns (CaptureResponseV2);
}
Events
`payment.captured.v2` (ядро) и `payment.enriched.v2` (детали).
На переходный период из продюсера идет `v1` и `v2`.
FAQ
Когда точно нужен `/v2`?
Когда меняются инварианты/семантика, удаляются поля/методы, меняется формат идентификаторов, ключ партиционирования, SLA/тайминги так, что ломаются клиенты.
Можно жить без явной версии в REST?
Только для мелких систем. Для внешних интеграций — лучше явные major-линии.
Какой срок держать устаревшую версию?
Зависит от экосистемы. Для внешних партнеров обычно 6–12 месяцев с ранним уведомлением и канарейкой.
Чем версионирование событий отличается от API?
События — append-only; новая семантика = новый тип `.v2` и dual-emit. Ключ партиционирования — часть контракта.
Итог
Стратегии версионирования — это процесс и инструменты: аддитивная эволюция по умолчанию, ясные major-линии, capability-negotiation, dual-run и sunset. Добавьте к этому автоматические проверки совместимости, телеметрию использования и дисциплину документации — и ваши API будут развиваться быстро, без «ночных миграций» и неожиданных падений у клиентов.