Skip to content
PПромтбук
RUEN
04Архитектура

API versioning: стратегия и deprecation

URL path vs Accept header vs media-type, deprecation timeline, sunset headers, как не застрять на v2 навсегда.

Спроектируй стратегию versioning для API: {{api_surface}}. Потребители: {{consumers}}.

Базовая аксиома: versioning — это контракт с будущим. Каждая новая версия — налог: документация, поддержка, миграции. Цель — менять API так, чтобы новой версии не понадобилось годами. Если у тебя уже есть v2, v3, v4 — это симптом, а не достижение.

1. Когда versioning вообще нужен

Не нужен:

  • Внутренний сервис, один consumer которого ты контролируешь — деплоишь оба вместе
  • API в alpha/beta с предупреждением "контракт может меняться"

Нужен:

  • Public API (third-party разработчики)
  • Mobile clients (старые версии не обновляются месяцами)
  • Partner integrations (контракты, SLA)

2. Где жить версии — три варианта

ПодходПримерПлюсыМинусы
URL path/v1/users/123Видно в логах/curl, легко роутить в gatewayВерсионируется весь API, не отдельные ресурсы
Accept headerAccept: application/vnd.api.v2+jsonИдеологически чисто (URL = ресурс)Невидимо в браузере, ломает кэширование, тяжело отлаживать
Query param?version=2ПростоСчитается анти-паттерном, путает кэширование

Рекомендация по умолчанию: URL path (/v1/). 95% public API так делают (Stripe, GitHub, Twilio). Accept header — только если у тебя HATEOAS-like дизайн.

Никогда: version в body ({"version": "2", ...}) — это уже не versioning, а маршалинг.

3. Что считать breaking change

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

  • Удаление поля, endpoint'а, query параметра
  • Переименование (path, поле, enum value)
  • Изменение типа поля (string → number, optional → required)
  • Изменение семантики (раньше 404 для несуществующих → теперь 200 с empty)
  • Ужесточение валидации (раньше принимали emoji в имени → теперь нет)
  • Изменение HTTP статуса (200 → 201, 400 → 422)
  • Изменение defaults

Non-breaking (можно в текущей версии):

  • Добавление optional поля в response
  • Добавление optional query/body параметра
  • Добавление нового endpoint
  • Добавление enum value (опасно — клиенты могут не знать о новом)
  • Расширение валидации (раньше max 100 символов → теперь 200)

Серая зона: добавление enum value. Если клиент делает switch(status) без default — сломаешь. Документируй как "may add new values; handle unknown gracefully".

4. Deprecation timeline

Правило: минимум 6 месяцев между объявлением и удалением. Для public API — 12+ месяцев.

T+0   ── Анонс депрекации (changelog, email partners, banner в docs)
T+1м  ── Sunset header в каждом response: Sunset: Sat, 31 May 2025 23:59:59 GMT
T+3м  ── Warning email тем, кто всё ещё использует v1 (нужны usage metrics)
T+5м  ── Brownout: рандомные 503 на 1 час раз в неделю (форсит миграцию)
T+6м+ ── Удаление, redirect на error page с инструкцией

Sunset header — RFC 8594:

HTTP/1.1 200 OK
Sunset: Sat, 31 May 2025 23:59:59 GMT
Deprecation: true
Link: <https://docs.api.com/migrate-v1-to-v2>; rel="sunset"

5. Версионирование на уровне ресурсов (когда URL не годится)

Иногда нужно: добавить новое поле формата, но старые клиенты не понимают новый формат. Варианты:

# Content negotiation per resource
GET /users/123
Accept: application/vnd.api.user.v2+json

Или field-level versioning:

{
  "id": 123,
  "name": "Alice",
  "address": "Old format string",
  "address_v2": { "street": "...", "city": "..." }
}

Уродливо, но позволяет не плодить v2 всего API ради одного поля. Удалить address через deprecation.

6. Как не дойти до v2

Большинство breaking changes можно избежать дизайном:

  • Никогда не возвращай array на root["a", "b"] нельзя расширить. Возвращай {"items": [...], "next_cursor": null}
  • Никогда не возвращай примитивtrue нельзя расширить. Возвращай {"ok": true}
  • Booleans → enums{"approved": true} нельзя расширить до состояний "pending/approved/rejected". Возвращай {"status": "approved"}
  • IDs всегда string — позволяет менять формат (int64 → UUID) без breaking
  • Дата всегда ISO 8601 string — не Unix timestamp number
  • Пагинация — cursor, не offset — позволяет менять backend без ломания пагинации
  • Default additionalProperties: false в схеме — клиенты не должны полагаться на неописанные поля

7. Поддержка нескольких версий в коде

Три подхода:

Параллельные пути:

/v1/users.controller.ts
/v2/users.controller.ts

Просто, но дублирование. Подходит когда v2 — большой передизайн.

Адаптеры:

// Single internal model, version-specific (de)serializers
v1Adapter.serialize(user) // → old format
v2Adapter.serialize(user) // → new format

Меньше дублирования, но адаптеры разрастаются.

Feature flags per version:

if (req.apiVersion >= 2) {
  response.address = structuredAddress;
} else {
  response.address = formatLegacyAddress(structuredAddress);
}

Опасно — версии переплетаются в одном файле. Только для маленьких различий.

8. Usage tracking — обязательно

Без usage metrics deprecation невозможна. Логируй на каждом запросе:

  • Версия API
  • Consumer (API key / OAuth client_id)
  • Endpoint
  • User agent (для mobile — версия приложения)

Dashboard: "сколько % трафика всё ещё на v1, кто эти клиенты, когда последний раз слышали от них". Без этого ты не знаешь когда удалять.

9. Что отдать на выходе

  • Решение: URL path vs Accept header (с обоснованием для {{consumers}})
  • Breaking change policy (что breaking, что нет)
  • Deprecation timeline (X месяцев, какие шаги)
  • Sunset/Deprecation headers формат
  • Design rules чтобы не дойти до v2 (списки выше)
  • Usage tracking план

Anti-patterns

  • ❌ Версия в каждом эндпоинте без единой стратегии (/users/v2, /api/v3/posts, ?ver=4) — нечитаемый зоопарк
  • ❌ Удаление endpoint'а без deprecation period — partner integrations падают, ты теряешь доверие
  • ❌ Bump major version ради добавления optional поля — нагружает всех консьюмеров миграцией без причины
  • ❌ Versioning только в gateway, internal сервисы не знают версию — нельзя сделать version-specific логику
  • ❌ Sunset header написан, но usage не трекается — удалишь то что ещё используется, или будешь поддерживать v1 вечно
  • ❌ v2 как "v1 + куча новых фич" — заставляет мигрировать ради фич, миграция big-bang
  • ❌ Версионирование без changelog с примерами до/после — клиенты не знают что менять
  • ❌ Поддержка 5+ версий одновременно — комбинаторный взрыв тестов и багов

В конце

  • Стратегия (URL vs header) + обоснование
  • Breaking change classification документ
  • Deprecation timeline (6/12 месяцев) + sunset header schema
  • Design guidelines (objects, не arrays; enums, не booleans; cursors, не offsets)
  • Usage tracking dashboard
  • Owners: кто решает «делаем v2», кто отвечает за миграцию консьюмеров
К подразделу «Архитектура»
Похожие промты