Версионирование API и обратная совместимость
Представь что у тебя тысяча клиентов которые используют твой API. Сегодня ты решаешь переименовать поле user_name в username. Казалось бы, мелочь. Но все тысяча клиентов сломаются в момент деплоя. Версионирование API, это способ развивать API не ломая тех кто уже на нём завязан.
Зачем нужно версионирование API
В процессе развития вашего продукта, API будет меняться. Вы можете экстренно поменять что-то, например, переименовать объект из-за изменения требований. Но если API изменяется без версионирования, это вызывает проблемы для клиентов. Если у вас один клиент, который использует ваш API, то сообщить ему о изменении – дело не хлопотное. Но если их тысячи или даже миллионы?
Частный случай: предположим, ваше приложение отправляет PUSH-уведомления для сервиса чата, и в определенный момент вы решаете, что не хватает каких-то данных в объекте сообщения. Вы добавляете поле message.id и возвращаете данные в ответе на этот запрос. Если клиент не обновился и не учитывает ваше изменение, в ответ вместо ожидаемого значения он получает боковое свойство с id полем. Если известно, что конечное свойство является целым, а теперь вместо него передано поле id, это вызовет ошибку.
Если вы меняете API без версионирования, пользователи ваших клиентских приложений будут сталкиваться с багами, и это вызовет недовольство вашей аудиторией. Это рискует привести к потере клиентов.
Примеры из реальной практики
Один из ярких примеров произошел с виртуальной кредитной системой Stripe. В середине 2018 года Stripe изменила API код для обработки карточных платежей. В реальности это привело к ошибкам на сайтах некоторых клиентов Stripe и вынудило компании откатывать свои системы. Это стоило им миллионов долларов.
Другой пример связан с изменением API в Facebook, из-за чего множество приложений, которые зависели от его API, перестали работать правильно. Это вынудило Facebook более тщательно подходить к управлению версиями своего API и разрабатывать стратегии deprecated feature.
Стратегии версионирования API
1. Версионирование в URL (Path versioning)
Этот метод предполагает включение номера версии в URL. Обычно версия указывается после префикса API, подобно /v1/, /v2/, /v3/.
Пример:
/api/v1/users
/api/v2/users
...
Плюсы:
- Читаемость URL.
- Легко отслеживать разные версии API.
Минусы:
- Усложнено управление версиями при крупных обновлениях, потому что все ссылки, указывающие на старую версию, остаются активными.
- Требует поднятия новых версий поочередно.
Когда использовать:
- Хорош для API, которые меняются редко.
2. Версионирование через заголовок (Header versioning)
Версию API прописываете в заголовке (Accept):
Accept: application/vnd.yourcompany.com-v2+json
Пример запроса/ответа:
GET /users HTTP/1.1
Authorization: Bearer <your_access_token>
Accept: application/vnd.yourcompany.com-v2+json
HTTP/1.1 200 OK
Content-Type: application/vnd.yourcompany.com-v2+json
[
...
]
Плюсы:
- Ваш URL всё время остается таким же.
- Возможно управление версиями на уровне клиента.
Минусы:
- Сложнее тестировать с помощью обычных браузеров или инструментов, таких как
curl. - Требуется поддержка заголовка
Acceptна сервере.
Когда использовать:
- Когда API требует очень большого количества версий и его сложно будет проконтролировать другим способом.
3. Версионирование через query parameter
Версию API можно передать через параметр строки запроса.
Пример:
/api/users?version=2
Плюсы:
- Относительно легко использовать и кэшировать.
- Не требует изменения структуры URL.
Минусы:
- Параметры версий могут накапливаться в истории адресной строки.
- Может возникать путаница у пользователей, особенно если есть и другие параметры.
Когда использовать:
- Когда версии API не меняются часто и не требуется глобальное управление версиями.
Таблица:
| Стратегия | Читаемость URL | Удобство кэширования | Сложность реализации | Используется в |
|---|---|---|---|---|
| Path versioning | Высокая | Среднее | Низкая | Разнообразные API |
| Header versioning | Низкая | Среднее | Средняя | API с множеством версий |
| Query parameter | Средняя | Высокая | Низкая | API с небольшой коичеством версий |
Breaking changes vs Non-breaking changes
Что такое breaking change (изменение, ломающее совместимость)
Это изменение в API, которое вызывает поломку существующих клиентов. Вот несколько примеров:
Удаление поля из ответа
// Прежде { "name": "John Doe", "email": "john.doe@example.com", "id": 123 } // После изменения { "name": "John Doe", "id": 123 }Если клиент ожидает поле
email, то не получив его, может сломаться.Изменение типа данных поля
{ "id": 123, // Число ... } { "id": "123", // Строка ... }Если клиент ждет число, но на самом деле получит строку, возникнут проблемы.
Изменение структуры ошибок
// Прежде { "error": "Something went wrong" } // После изменения { "errors": [ { "code": 1, "message": "Something went wrong" } ] }Переименование эндпоинта
// Прежде GET /items // После изменения GET /productsИзменение метода
// Прежде GET /items/delete/{id} // После изменения DELETE /items/{id}
Что такое non-breaking change (изменение, не ломающее совместимость)
Это изменение, которое не вызывает проблем у существующих клиентов. Примеры:
Добавление нового опционального поля
{ "name": "John Doe", "email": "john.doe@example.com", "id": 123, "phone": "123-456-7890" // Н ew field }Добавление нового эндпоинта
// Прежде GET /users // После изменения GET /users POST /usersДобавление нового query параметра
// Прежде GET /users // После изменения GET /users?order=asc
Таблица:
| Тип изменения | Breaking change | Non-breaking change |
|---|---|---|
| Удаление поля | Да | Нет |
| Изменение типа данных поля | Да | Нет |
| Изменение структуры ошибок | Да | Нет |
| Переименование эндпоинта | Да | Нет |
| Изменение метода | Да | Нет |
| Добавление нового опционального поля | Нет | Да |
| Добавление нового эндпоинта | Нет | Да |
| Добавление нового query параметра | Нет | Да |
Semver для API (Semantic Versioning)
Semver (Semantic Versioning – Семантическое версионирование) – это набор правил, описывающих, как должен выглядеть номер версии вашего API. Semver базируется на формате: MAJOR.MINOR.PATCH.
MAJORверсия увеличивается при внесении изменений, несовместимых с предыдущими версиями (breaking changes).MINORверсия увеличивается, если были добавлены новые функции, но при этом несовместимые изменения не вносятся.PATCHверсия увеличивается при исправлении багов, не изменяющих функциональность API.
Стратегия deprecation
Как правильно устаревать версии
Когда вы выпустили новую версию API и хотите сделать старую версией устаревшей (deprecation и, в конечном итоге, удалить её), вот основные шаги, которыми стоит руководствоваться:
Анонс через документацию и changelog
Сообщите о предстоящем deprecation в документации и обновите changelog.
Заголовок Deprecation и Sunset в ответах
Включите заголовки, которые указывают на устаревание версии и дату её окончательного удаления (Sunset):
Deprecation: Sat, 01 Jan 2027 00:00:00 GMT Sunset: Sat, 01 Jan 2027 00:00:00 GMT Link: <https://api.example.com/v3/users>; rel="successor-version"Email-уведомления клиентам
Выслать email-уведомления клиентам о deprecation и предложить переход на новую версию API.
Sunset period, сколько держать старую версию
Обычно period устаревания составляет от 3 до 12 месяцев, в зависимости от размера вашего клиентского база и сложности перехода.
Обратная совместимость на практике
Правило расширяй, не меняй
"Always add, never remove", это золотое правило проектирования интерфейсов API и другого программного обеспечения, требующего стабильности.
Это означает, что при изменении API следует придерживаться следующих принципов:
- Не удаляйте файлы, методы, свойства, функции, константы интерфейса без deprecation.
- Добавляйте новые, но не изменяйте существующие.
- Изменение типа данных в существующих свойствах может привести к проблемам совместимости.
- Предусмотрите возможность откат назад, если это возможно.
Versioned response через Content Negotiation
Content Negotiation, метод определения формата данных (в данном случае, версии API) обмена между клиентом и сервером. Клиент указывает, какой тип содержимого он ожидает через заголовок Accept.
Пример реализации на Node.js/Express:
Маршруты:
app.get('/api/users', (req, res) => {
const version = req.headers['accept-version'];
if (version === 'v2') {
res.json({ version: 'v2', users: [...] });
} else {
res.json({ version: 'v1', users: [...] });
}
});
Запрос:
GET /api/users HTTP/1.1
Accept-Version: v2
HTTP/1.1 200 OK
Content-Type: application/json
{
"version": "v2",
"users": [
...
]
}
Реальные примеры из индустрии
Stripe API
Stripe, одна из ведущих платёжных систем в мире, изменила подход к версионированию API, использовав даты вместо номеров версий (например, 2023-10-16 вместо v3). Это означает, что, если изменение произошло 16 октября 2023 года, тогда версия будет 2023-10-16. Это делает дату выпуска API прозрачной для конечных пользователей.
Основная причина этого было то, что номер версии API Stripe передавало информацию о том, что версия устарела. С датой это становится ясным для всех: дата указывает, когда эта версия станет deprecated.
Stripe также предоставляет инструменты миграции для пользователей, чтобы они могли обновить свои интеграции без лишних проблем.
GitHub API
GitHub использует заголовок Accept для указания версии API. Вы можете использовать версию в формате семантического версионирования, например:
Accept: application/vnd.github.v3+json
GitHub также предлагает guide для миграции между версиями.
Twilio API
Twilio, это облачная коммуникационная платформа, предоставляющая API для работы с SMS, VoIP и др. Twilio придерживается подхода к версионированию похожего на Stripe. Они используют дату в заголовке Accept:
Accept: application/vnd.twilio.v1+json; date=2023-10-16
Это позволяет Twilio внести изменения в свой API, сохраняя при этом обратную совместимость, и клиенты могут обновлять свои интеграции на свой выбор.
Migration Guide для клиентов
Когда вы выпускаете новую версию API, очень важно предоставить detailed guide вашим клиентам, как они могут выполнить переход от старой версии к новой.
Пример template guide:
Title: Migration Guide to API v2
Introduction:
В этом руководстве описаны изменения, которые вы должны учесть при обновлении вашего приложения до API v2.
Breaking Changes:
Изменение формата даты в ответе.
Эндпоинт
/api/v1/usersизменен на/api/v2/users.
Non-Breaking Changes:
Добавлен новый эндпоинт
/api/v2/orders.Добавлен новый параметр
sortдля сортировки списка пользователей.
Code Samples:
Пример 1:
// Прежде
fetch('/api/v1/users')
.then(response => response.json())
.then(users => console.log(users.date))
// После
fetch('/api/v2/users')
.then(response => response.json())
.then(users => console.log(users.new_date_format))
Пример 2:
// Новый эндпоинт
fetch('/api/v2/orders')
.then(response => response.json())
Deprecation Date:
Старая версия API будет deprecated к 01 January 2027 00:00:00 GMT.
Contact:
Если у вас возникнут вопросы, свяжитесь с нашей командой поддержки по адресу: support@example.com.
Чеклист перед выпуском новой версии API
Перед публикацией новой версии API рекомендуется пройти следующий чеклист:
- Обратная совместимость: Убедитесь, что все non-breaking changes на самом деле не ломают существующих клиентов.
- Documentation: Обновите все документы, включая ваш API documentation, guide по миграции, changelog.
- Testing: Проведите исчерпывающее testing всей системы, включая unit-тесты, интеграционные и regression-тесты.
- Feedback: Получите feedback от ваших клиентов, возможно, проведя beta-тестирование.
- Deprecation: Убедитесь, что все Breaking changes были объявлены как deprecated в предыдущей версии API.
- Communication: Убедитесь, что анонс вышел о новых версиях и deprecated features, включая email, блог.
- Migration Plan: Сформируйте план перевода ваших клиентов на новую версию API.
- Monitoring: Убедитесь, что ваши monitoring tooling установлены для новой версии API.
- Rollback: Убедитесь, что подготовлен план откачива, если что-то пойдет не так.
- Compliance: Убедитесь, что ваш API corresponds to все compliance и regulatory требования.
Соблюдая все эти шаги и принимая решения, основанные на данных, вы создадите надежный и стабильный API, который будет удовлетворять потребностям ваших клиентов и пользователей.