Как эволюционировать схему REST API при добавлении новых полей без нарушения совместимости между сервисами
Что проверяют
Отличаете ли вы совместимое изменение от ломающего и понимаете ли, что для нового поля отдельная версия API обычно не нужна. Следом спрашивают, как переименовать поле или сделать его обязательным и как вы узнаете, что сломали соседний сервис.
Как отвечать
Базовое правило: добавлять можно, убирать и менять нельзя. Совместимы новое поле в ответе, новый необязательный параметр запроса, новый эндпоинт. Ломают совместимость удаление и переименование поля, смена типа или формата, превращение необязательного поля в обязательное, а для строгих клиентов ещё и новое значение в enum.
Работает правило при двух условиях:
- терпимый читатель (Tolerant Reader): клиент игнорирует незнакомые поля. В Pydantic v2 так и есть по умолчанию (
extra="ignore"), а схема marshmallow по умолчанию бросаетValidationErrorна неизвестном поле, и распаковкаDataclass(**data)падает сTypeError; - новое поле запроса необязательно: у него есть значение по умолчанию на сервере, в модели FastAPI это
field: str | None = None. Старые клиенты его не пришлют, и сервер должен вести себя как раньше.
Порядок выкатки тоже важен: сначала сервер учится принимать поле, потом клиенты начинают его слать. Переименование делают через expand and contract: добавили новое поле рядом со старым, заполняете оба, перевели потребителей, по метрикам убедились, что старое никто не читает, и только тогда удалили. Дату отключения можно сообщить заголовком Sunset.
Поломки лучше ловить автоматически: сравнивать в CI OpenAPI-схему от FastAPI с предыдущей (например, oasdiff), а договорённости между сервисами проверять контрактными тестами на Pact. Путь /v2 нужен для настоящего ломающего изменения, а не для каждого нового поля.
Где спотыкаются
Заводят /v2 ради одного поля. Через год приходится поддерживать три копии одного API.
Путают отсутствие поля и null. Для клиента, который различает «не передано» и «очищено», это разная семантика, и в Pydantic её приходится явно различать через exclude_unset.
Проверяют только свой код. Клиент со строгой моделью extra="forbid" или своим валидатором схемы упадёт на новом поле, и узнаете вы об этом с прода.