Перейти к содержимому

Версионирование API

Версионирование API — управление изменениями без поломки клиентов. Ломающие изменения (удаление/переименование поля, смена типа) требуют новой версии. Не ломающие (добавление поля) — нет. Стратегии: URL path (/v1/users — самый популярный, простой, кешируется), query param (?version=1), custom header (Api-Version: 1), content negotiation (Accept: application/vnd.myapi.v1+json), domain (v1.api.example.com). Устаревание (deprecation): заголовки Deprecation, Sunset, Link. Поддерживать не более 2-3 версий.

Введение: Эволюция без разрушения

Представьте, что вы выпустили мобильное приложение. В первой версии у пользователя были поля: имя, email, телефон. Во второй версии вы добавили адрес и убрали телефон. Что произойдёт со старыми версиями приложения, которые всё ещё ожидают поле “телефон”? Они сломаются.

API, как и любое программное обеспечение, эволюционирует. Появляются новые требования, меняется бизнес-логика, оптимизируются запросы. Но клиенты (мобильные приложения, веб-сайты, интеграции) не обновляются мгновенно. У некоторых пользователей может стоять версия приложения, выпущенная год назад.

Версионирование API — это практика управления изменениями в API, позволяющая клиентам продолжать работать по-старому, пока они не обновятся. API не ломается — он развивается, оставляя обратную совместимость.

Без версионирования любое изменение API может сломать клиентов. С версионированием клиенты могут выбирать, какую версию API использовать, и переходить на новые версии в своём темпе.

Почему API нужно менять

API не статичен. Он меняется по многим причинам:

ПричинаПример
Новые требования бизнесаДобавить поле “промокод” в заказ
Улучшение производительностиОбъединить два запроса в один
Исправление ошибок в дизайнеПереименовать непонятное поле
Удаление устаревших полейПоле “fax” больше не нужно
Изменение формата данныхДата из строки в объект
Изменение бизнес-правилМинимальная сумма заказа выросла

Что ломает обратную совместимость

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

ИзменениеПримерЛомает?
Удаление поляУбрали fax из ответаДа
Переименование поляuser_namefullNameДа
Изменение типа поляcreated_at из строки в числоДа
Изменение структурыПлоский объект → вложенныйДа
Изменение URL/users/customersДа
Изменение методаPOSTPUTДа
Изменение статус-кода200404 для “не найдено”Да
Ужесточение валидацииРаньше name мог быть пустым, теперь нетДа

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

ИзменениеПримерЛомает?
Добавление нового поляДобавили phone в ответНет
Добавление нового эндпоинтаДобавили /users/{id}/ordersНет
Расширение допустимых значенийstatus теперь может быть “pending”Нет (если клиент игнорирует новые значения)
Смягчение валидацииРаньше name был обязательным, теперь нетНет
Добавление параметра со значением по умолчанию?include_details=falseНет

Стратегии версионирования

СтратегияПримерПлюсыМинусы
URL path/v1/usersПросто, видно, кешируется“Загрязняет” URL
Query parameter/users?version=1ПростоНе кешируется, не RESTful
Custom headerAccept-version: v1Чистый URLНе видно, сложнее тестировать
Content negotiationAccept: application/vnd.myapi.v1+jsonСтандартный, RESTfulСложный, не интуитивный
Domainv1.api.example.comПолная изоляцияСложная инфраструктура

1. Версионирование в URL (Path)

Самый популярный и простой подход. Версия включается в путь ресурса.

GET /v1/users/123
GET /v2/users/123

Пример реализации

# Запрос старой версии
GET /v1/users/123
{
    "user_id": 123,
    "user_name": "Иван",
    "email": "ivan@example.com"
}
# Запрос новой версии (добавили phone, переименовали поля)
GET /v2/users/123
{
    "id": 123,
    "name": "Иван",
    "email": "ivan@example.com",
    "phone": "+7-999-123-45-67"
}

Преимущества

ПреимуществоОбъяснение
ПростотаЛегко понять, какая версия используется
ВидимостьВерсия видна в логах, curl, браузере
КешированиеРазные URL кешируются независимо
Простое тестированиеМожно тестировать разные версии вручную

Недостатки

НедостатокОбъяснение
Загрязнение URLВерсия не является частью ресурса
Дублирование кодаНужно поддерживать несколько версий
Не RESTfulРесурс /users/123 — это один ресурс, но v1 и v2 — разные

2. Версионирование в query параметре

Версия передаётся как параметр запроса.

GET /users/123?version=1
GET /users/123?version=2

Пример

GET /users/123?version=1
{
    "user_id": 123,
    "user_name": "Иван"
}
GET /users/123?version=2
{
    "id": 123,
    "name": "Иван"
}

Преимущества и недостатки

ПлюсыМинусы
ПростотаНе кешируется (кеш по полному URL, параметр меняет URL)
Чистый URLНе RESTful (версия — не часть ресурса)
Легко реализоватьВерсия не видна в логах по умолчанию

3. Версионирование в заголовке (Custom Header)

Версия передаётся в специальном заголовке.

GET /users/123
Api-Version: 1
GET /users/123
Api-Version: 2

Преимущества и недостатки

ПлюсыМинусы
Чистый URLНе видно в браузере/curl (нужен специальный заголовок)
RESTful (URL не меняется)Сложнее тестировать
Кеширование может учитывать заголовокЗаголовок не стандартизирован

4. Версионирование через Content Negotiation (Accept header)

Использует стандартный HTTP механизм согласования содержимого.

GET /users/123
Accept: application/vnd.myapi.v1+json
GET /users/123
Accept: application/vnd.myapi.v2+json

Пример

GET /users/123
Accept: application/vnd.myapi.v1+json
{
    "user_id": 123,
    "user_name": "Иван"
}
GET /users/123
Accept: application/vnd.myapi.v2+json
{
    "id": 123,
    "name": "Иван"
}

MIME тип для версионирования

Формат: application/vnd.{company}.{api-name}.v{version}+{format}

КомпонентПример
vndVendor (стандартное обозначение)
companymycompany
api-nameusers
versionv1, v2
formatjson, xml

Преимущества и недостатки

ПлюсыМинусы
Стандартный HTTP подходСложный для понимания
RESTful (URL не меняется)Неудобно тестировать в браузере
Разделение версии и форматаТребует парсинки Accept заголовка

5. Версионирование через домен (Subdomain)

Разные версии API на разных доменах.

GET https://v1.api.example.com/users/123
GET https://v2.api.example.com/users/123

Преимущества и недостатки

ПлюсыМинусы
Полная изоляция версийСложная инфраструктура (DNS, SSL)
Можно развернуть независимоНесколько доменов для клиентов
Простота для клиентаМиграция между версиями требует смены домена

Сравнение стратегий

ХарактеристикаURL pathQuery paramHeaderContent negot.Domain
Простота★★★★★★★★★☆★★★☆☆★★☆☆☆★★★☆☆
Кеширование★★★★★★★★☆☆★★★★☆★★★★☆★★★★★
RESTful★★★☆☆★★☆☆☆★★★★☆★★★★★★★★☆☆
Видимость★★★★★★★★★☆★★☆☆☆★★☆☆☆★★★★★
Удобство тестирования★★★★★★★★★☆★★☆☆☆★★☆☆☆★★★★☆
Популярность★★★★★★★★☆☆★★★☆☆★★☆☆☆★★★☆☆

Вывод: URL path — самый популярный выбор для большинства API. Content negotiation — самый RESTful, но сложный. Domain — для крупных API с полной изоляцией версий.

Устаревание версий (Deprecation)

Старые версии API не могут существовать вечно. Нужен процесс их вывода из эксплуатации.

Процесс устаревания

    graph LR
    A[Новая версия] --> B[Deprecation<br>объявление]
    B --> C[Deprecation<br>период]
    C --> D[Удаление версии]
  

Заголовок Deprecation

HTTP/1.1 200 OK
Deprecation: true
Sunset: Sat, 31 Dec 2025 23:59:59 GMT
Link: <https://api.example.com/v2/users/123>; rel="successor-version"
ЗаголовокЗначение
DeprecationУказывает, что API устарел
SunsetДата удаления
LinkСсылка на новую версию

Предупреждения в теле ответа

{
    "data": {...},
    "warnings": [
        {
            "code": "DEPRECATION",
            "message": "API version 1 is deprecated and will be removed after 2025-12-31. Please migrate to version 2.",
            "link": "https://api.example.com/docs/migration-v1-to-v2"
        }
    ]
}

Сроки устаревания

Тип APIТипичный срок
Публичные API6-12 месяцев
Партнёрские API3-6 месяцев
Внутренние API1-3 месяца

Практические примеры

Пример 1: Эволюция пользовательского API

Версия 1 (исходная):

GET /v1/users/123
{
    "user_id": 123,
    "user_name": "Иван",
    "email": "ivan@example.com"
}

Версия 2 (добавили phone, переименовали поля):

GET /v2/users/123
{
    "id": 123,
    "name": "Иван",
    "email": "ivan@example.com",
    "phone": "+7-999-123-45-67"
}

Версия 3 (изменили структуру адреса):

GET /v3/users/123
{
    "id": 123,
    "name": "Иван",
    "email": "ivan@example.com",
    "phone": "+7-999-123-45-67",
    "address": {
        "city": "Москва",
        "street": "Тверская",
        "zip": "101000"
    }
}

Пример 2: Одна версия в URL, другая в Accept

# Базовая версия
GET /users/123

# Конкретная версия через Accept
GET /users/123
Accept: application/vnd.myapi.v2+json

Пример 3: Версионирование через тип ресурса

# Старый формат
GET /users/123?format=old

# Новый формат
GET /users/123?format=new

Миграция между версиями

Адаптеры (Transformation layer)

Внутри сервера старый и новый API могут использовать одну бизнес-логику, но с разными адаптерами.

# Псевдокод
def v1_user_response(user):
    return {
        "user_id": user.id,
        "user_name": user.name,
        "email": user.email
    }

def v2_user_response(user):
    return {
        "id": user.id,
        "name": user.name,
        "email": user.email,
        "phone": user.phone
    }

Полная изоляция

Разные версии — разные кодовые базы, разные серверы.

v1.api.example.com → сервер с кодом версии 1
v2.api.example.com → сервер с кодом версии 2

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

Ошибка 1: Отсутствие версионирования с первого дня

GET /users/123

Когда нужно внести ломающее изменение, невозможно.

Как исправить: Создайте /v1/users/123 и оставьте /users/123 как редирект на текущую версию.

Ошибка 2: Версионирование в теле запроса

POST /users
{"version": 1, "name": "Иван"}

Почему плохо: Версия должна быть видна до обработки тела (для маршрутизации, логирования).

Как исправить: URL или заголовок.

Ошибка 3: Версионирование каждого ресурса отдельно

GET /v1/users/123
GET /v2/orders/456

Почему плохо: Клиент должен знать, какая версия для какого ресурса.

Как исправить: Одна версия для всего API.

Ошибка 4: Слишком много версий

Версии v1, v2, v3, v4, v5, v6, v7 — все поддерживаются.

Как исправить: Устаревание старых версий. Максимум 2-3 активные версии.

Ошибка 5: Игнорирование миграции клиентов

Объявили deprecation, но клиенты не переходят.

Как исправить: Мониторинг использования, коммуникация с клиентами, продление сроков.

Резюме для системного аналитика

  1. Версионирование API — необходимость. API меняется, клиенты не обновляются мгновенно. Без версионирования изменения ломают клиентов.

  2. Ломающие изменения требуют новой версии. Не ломающие изменения (добавление полей) можно делать без смены версии.

  3. URL path — самый популярный подход. Прост, виден, кешируется. GET /v1/users/123.

  4. Другие подходы: query parameter (?version=1), custom header (Api-Version: 1), content negotiation (Accept: application/vnd.myapi.v1+json), domain (v1.api.example.com).

  5. Устаревание (deprecation) — процесс вывода старых версий из эксплуатации. Используйте заголовки Deprecation, Sunset, Link. Давайте клиентам время на миграцию (месяцы, не дни).

  6. Поддерживайте не более 2-3 версий одновременно. Старые версии удаляйте после периода устаревания.

Проверка знаний

Вопрос 1 из 4
Зачем API нужно версионирование?
Какое изменение обычно считается breaking change?
Какая стратегия версионирования самая популярная и понятная?
Что означает deprecation API-версии?

Вопросы, где были ошибки