Как расширить REST метод с двумя аргументами до трех без поломки клиентов
Что проверяют
Понимаете ли вы, что считается ломающим изменением контракта API и как добавлять возможности, не заставляя клиентов обновляться одновременно с сервером. Следом спрашивают, что делать, если новый параметр по смыслу обязательный.
Как отвечать
Главное правило: новый параметр добавляется необязательным, а его отсутствие означает ровно прежнее поведение.
- где передать: для
GETэто новый query-параметр, дляPOSTиPATCHновое поле в JSON-теле. Добавлять сегмент в путь нельзя, старые URL перестанут совпадать; - значение по умолчанию: в FastAPI это
param: int | None = Noneили поле Pydantic-модели с дефолтом, в DRFrequired=Falseсdefaultу поля сериализатора. Дефолт должен давать тот же результат, что и раньше; - ответ: поля только добавляются, существующие не переименовываются и не меняют тип. Клиенты при этом должны игнорировать незнакомые поля;
- обязательный параметр: тогда это новая версия. Выпускается
/v2/или версия через заголовок, старый метод продолжает жить, помечается устаревшим (заголовкиDeprecationиSunset, запись в changelog) и отключается по графику, когда логи покажут, что им больше не пользуются; - контроль: схема OpenAPI в репозитории и проверка диффа схем в CI, например
oasdiff, плюс контрактные тесты на старые запросы.
Если вопрос про саму Python-функцию за эндпоинтом, правило то же: новый аргумент добавляется последним, с дефолтом, лучше keyword-only через *, чтобы позиционные вызовы не поехали.
Где спотыкаются
Делают новый параметр обязательным, и валидация начинает отвечать 422 всем старым клиентам.
Забывают про PUT: он заменяет ресурс целиком, и старый клиент, который про новое поле не знает, при каждом обновлении будет затирать его значением по умолчанию. Здесь спасает PATCH или сохранение текущего значения, если поле не пришло.
Меняют смысл без изменения сигнатуры: параметры остались прежними, но дефолт стал другим. Формально совместимо, фактически клиенты получают другие данные.