Версионирование 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_name → fullName | Да |
| Изменение типа поля | created_at из строки в число | Да |
| Изменение структуры | Плоский объект → вложенный | Да |
| Изменение URL | /users → /customers | Да |
| Изменение метода | POST → PUT | Да |
| Изменение статус-кода | 200 → 404 для “не найдено” | Да |
| Ужесточение валидации | Раньше 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 header | Accept-version: v1 | Чистый URL | Не видно, сложнее тестировать |
| Content negotiation | Accept: application/vnd.myapi.v1+json | Стандартный, RESTful | Сложный, не интуитивный |
| Domain | v1.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: 1GET /users/123
Api-Version: 2Преимущества и недостатки
| Плюсы | Минусы |
|---|---|
| Чистый URL | Не видно в браузере/curl (нужен специальный заголовок) |
| RESTful (URL не меняется) | Сложнее тестировать |
| Кеширование может учитывать заголовок | Заголовок не стандартизирован |
4. Версионирование через Content Negotiation (Accept header)
Использует стандартный HTTP механизм согласования содержимого.
GET /users/123
Accept: application/vnd.myapi.v1+jsonGET /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}
| Компонент | Пример |
|---|---|
vnd | Vendor (стандартное обозначение) |
company | mycompany |
api-name | users |
version | v1, v2 |
format | json, 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 path | Query param | Header | Content 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 | Типичный срок |
|---|---|
| Публичные API | 6-12 месяцев |
| Партнёрские API | 3-6 месяцев |
| Внутренние API | 1-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, но клиенты не переходят.
Как исправить: Мониторинг использования, коммуникация с клиентами, продление сроков.
Резюме для системного аналитика
Версионирование API — необходимость. API меняется, клиенты не обновляются мгновенно. Без версионирования изменения ломают клиентов.
Ломающие изменения требуют новой версии. Не ломающие изменения (добавление полей) можно делать без смены версии.
URL path — самый популярный подход. Прост, виден, кешируется.
GET /v1/users/123.Другие подходы: query parameter (
?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 версий одновременно. Старые версии удаляйте после периода устаревания.