Версионирование API Должно Быть Последним Средством. Продолжение
Начало
Правила совместимости
- Сохраняйте существующие поля и поведение;
- Не превращайте необязательные данные запроса в обязательные;
- Не меняйте то, что делает существующая операция;
- Делайте всё новое в добавление и необязательным по умолчанию.
1. Добавляйте, а не заменяйте
Самое безопасное изменение обычно является аддитивным. Допустим, первоначальный ответ от
GET /orders/{id} был таким:{
"id": "ord_123",
"status": "paid",
"total": 100
}Вместо замены поля
total добавим новое:{
"id": "ord_123",
"status": "paid",
"total": 100,
"totalMoney": {
"amount": 100,
"currency": "USD"
}
}Существующие клиенты продолжают использовать
total. Новые могут перейти на totalMoney. Помечаем старое поле как устаревшее и удалим его только после реального периода миграции. Иногда некрасивый контракт — это цена совместимости.2. Делайте клиентов толерантными
Хорошо работающий клиент не должен выдавать ошибку из-за того, что сервер добавил поле, которое он не понимает. Если ответ изменился с:
{
"id": "ord_123",
"status": "paid"
}на:
{
"id": "ord_123",
"status": "paid",
"estimatedDeliveryDate": "2026-05-29"
}существующие клиенты должны игнорировать дополнительное свойство и продолжать работу.
В System.Text.Json неизвестные свойства игнорируются по умолчанию. Реальный риск обычно заключается в чрезмерно строгой проверке JSON (об этом позже на канале). Это одна из распространённых проблем. Команды заявляют о желании обратной совместимости, а затем генерируют клиентские модели, которые отклоняют любое неожиданное поле в ответе. Клиенты должны быть достаточно толерантными, чтобы игнорировать то, чего они не понимают.
3. Не меняйте то, что делает существующая операция
Самые опасные критические изменения скрываются в поведении. URL, тело запроса, формат ответа те же. Но то, что делает операция на сервере, отличается. Например,
DELETE /orders/{id} сначала реализовывал мягкое удаление, и заказ переходил в «архив», но по-прежнему отображался в отчётах аудита и мог быть восстановлен службой поддержки. Затем команда решила «почистить базу» и изменить поведение на жёсткое удаление. Ни один клиент сразу этого не заметит, но данные теперь «по-тихому» уничтожаются.Аналогично:
-
POST /orders раньше был идемпотентным, а затем незаметно перестал им быть;-
POST /orders/{id}/cancel раньше автоматически возвращал деньги, а затем перестал это делать, потому что «возвраты должны быть отдельным вызовом»;-
PUT /orders/{id} раньше был полной заменой, а теперь стал частичным слиянием;- Веб-хук раньше срабатывал один раз для каждого заказа, а теперь срабатывает для каждой позиции заказа;
и т.п.
Каждый из этих вариантов сохраняет стабильность URL, метода и структуры JSON, но нарушает все существующие интеграции таким образом, что это не будет видно при сравнении схем. Безопасный шаг тот же: добавлять, а не изменять. Например, параметр в конечной точке для жёсткого удаления
DELETE /orders/{id}?purge=trueКак только операция выпущена, её поведение становится частью контракта. Вы можете добавлять новые операции, можете объявить её устаревшей, но не можете незаметно изменять её работу.
4. Будьте осторожны с валидацией
Существует два варианта одной и той же ошибки:
- Сделать обязательным существующее необязательное поле;
- Добавить новое обязательное поле.
Оба варианта ломают существующих клиентов. Путь к конечной точке не меняется, но запросы, которые раньше выполнялись успешно, теперь отклоняются. Более безопасный путь — определить значения по умолчанию или ввести новую операцию для более строгого рабочего процесса.
Изменения в ответах обычно тщательно проверяются с точки зрения проектирования. Изменения в проверке запросов заслуживают такого же внимания. Главное правило: то, что вы добавляете в контракт, должно быть необязательным, и всё, что было необязательным, должно оставаться необязательным.
Окончание следует…
Источник: https://www.milanjovanovic.tech/blog/api-versioning-should-be-your-last-resort