Кросс-индустрияИнтеграции и API

Контракт API: как согласовать обязательные поля, ошибки и совместимые изменения до интеграции

Интеграция ломается не в момент, когда два сервиса впервые обмениваются JSON, а раньше: когда обязательность поля, код ошибки или новая версия остаются незафиксированным предположением.

Maria Novikova
NTA's digital editorial persona. Materials are based on the company's expertise and reviewed by the relevant team.
September 8, 2026·5 min read·0 comments
/ Screenshots and video
2 materials

JSON не является контрактом

В понедельник команда видит привычную задачу: сервис заказов должен передать сервису клиентов идентификатор клиента. В одном репозитории появляется customer_id, во втором — обработчик. Первые тестовые запросы проходят. Через месяц один сервис делает поле обязательным, другой продолжает отправлять старую форму, а третий читает ошибку как обычный ответ. Формально все работали с одним endpoint; фактически у них были разные договорённости.

OpenAPI определяет язык описания HTTP API, который позволяет человеку и инструментам понять возможности сервиса без чтения исходного кода. Это полезное начало, но не конец работы архитектора: спецификация фиксирует форму, а команда должна ещё назвать смысл данных, владельца и переход между версиями. В описании OpenAPI поле, не помеченное как required или обязательное в нормативном тексте, рассматривается как optional; для предметного API этого недостаточно. Нужно решить, что означает отсутствие значения и кто вправе это решение менять.

Полезно относиться к контракту как к наблюдаемому обещанию. У него есть как минимум четыре части: запрос, успешный ответ, ошибочный ответ и правила изменения. Любая из них может сломать потребителя. Поэтому «мы добавили только одно поле» не является оценкой риска. Важно, где поле живёт, требуется ли оно на входе, могут ли его игнорировать старые клиенты и какой ответ получит клиент при нарушении правила.

Поле — не строка JSON, а правило

У поля есть минимум пять вопросов. Первый — назначение: зачем оно нужно получателю и какую операцию меняет. Второй — направление: поле присылает клиент, формирует сервер или оба варианта допустимы. Третий — обязательность: отсутствующее значение означает «неизвестно», «не применять», «использовать значение по умолчанию» или ошибку. Четвёртый — форма: тип, формат, диапазон, enum, нормализация и nullability. Пятый — потребители: кто уже читает или формирует это значение.

JSON Schema даёт проверяемые средства для части этого разговора: required, типы, перечисления и иные ограничения. Но схема не знает, разрешено ли бухгалтерскому сервису подставлять идентификатор клиента из собственного справочника, сколько версий клиента организация поддерживает и можно ли вернуть значение назад. Это предметные и операционные правила; их нужно записать рядом со схемой.

Возьмём условный вход customer_id. Запись «string, required» ещё оставляет вопросы. Строка пустая допустима? Идентификатор стабилен при объединении карточек? Разрешён ли null в ответе, если клиент удалён? Можно ли клиенту передать идентификатор, который он не видит по правам? В материале нет универсальных ответов, потому что их определяет домен. Но контракт обязан показать, где именно такой ответ будет принят и кем подтверждён.

Практическая формулировка лучше начинается не с названия поля, а с инварианта: «Для изменения заказа отправитель передаёт идентификатор клиента, доступный в его контуре; при отсутствии или недопустимости сервис возвращает документированную ошибку». После этого тип и формат становятся способом проверить правило, а не декоративными свойствами JSON.

Ошибка — отдельная ветвь интерфейса

Команда часто подробно описывает 200, а ошибки оставляет в примере «что-то пошло не так». Это переносит работу на потребителя: он вынужден угадывать, повторять запрос, просить пользователя исправить форму или открыть инцидент. HTTP-семантика определяет статус как часть ответа; статус не стоит прятать в успешный ответ с полем error только потому, что так удобнее одному клиенту.

RFC 9457 описывает переносимый формат Problem Details для HTTP API. Его базовые члены включают type, title, status и detail. Стандарт не предписывает бизнес-каталог ошибок, но даёт общий каркас: клиент может определить класс проблемы, показать короткое имя, сопоставить его с HTTP-статусом и использовать подробность для действия. Расширения допустимы, если их смысл также документирован.

Ошибка не должна выдавать детали, которые помогут обойти контроль или раскроют внутреннее устройство. Не нужно возвращать SQL, токен, трассировку или факт существования объекта там, где это само чувствительная информация. Поэтому у ошибки в карточке есть не только status и detail, но также владелец и классификация: можно ли текст показать пользователю, что логировать, что повторять, а что сразу направлять на ручную обработку.

Для одного endpoint обычно полезнее перечислить несколько реальных ветвей, чем собрать большой каталог статусов. Например: неверная форма входных данных; объект не найден или недоступен; конфликт состояния; временная недоступность зависимости. Для каждой ветви нужно назвать HTTP-статус, стабильный машинный код или URI типа, безопасный для клиента detail, повторяемость запроса и тест. Это не делает интеграцию безошибочной, но убирает привычный спор «почему клиент так обработал ответ».

Совместимость — это сохранённое обещание

Слово «обратно совместимо» слишком легко произнести и слишком трудно проверить. Если сервер требует новое входное поле, старый клиент не умеет его отправить — прежнее обещание уже нарушено. Если сервер добавляет поле в ответ, клиент, который строго запрещает неизвестные ключи, может перестать десериализовать ответ. Если изменяется enum, потребитель с исчерпывающим switch может не предусмотреть новое значение. Если меняется значение ошибки, автоматический retry может стать опасным.

Поэтому изменение рассматривают с двух сторон. Для отправителя спрашивают: сможет ли старая версия сформировать допустимый запрос? Для получателя: сможет ли старая версия принять допустимый ответ? Эти вопросы не сводятся к diff спецификации. Нужны сведения о реально поддерживаемых клиентах, их настройках десериализации и сроке перехода.

Небольшое изменение обычно безопаснее проводить в два шага. Сначала сервер начинает понимать новый необязательный элемент или сохраняет прежнюю форму ответа. Затем потребители начинают использовать новое поведение. Только после подтверждённой миграции и заранее названной даты можно сделать правило обязательным или убрать старое. В отдельных случаях разумнее честно выпустить новую версию API и дать потребителю маршрут переключения; это не провал, а признание разрыва обещания.

OpenAPI сам подчёркивает, что deprecated элементы остаются частью описания, пока не заменены; это полезная инженерная дисциплина. Метка deprecated не удаляет поведение автоматически. Она должна сопровождаться заменой, владельцем, датой пересмотра, потребителями и наблюдением за тем, кто ещё использует старый путь.

Карточка изменения до реализации

Для одного endpoint достаточно короткой карточки, если в ней нет «и так понятно». Ниже — шаблон, который можно положить в ADR, задачу или репозиторий рядом со спецификацией.

Раздел Что зафиксировать Проверочный вопрос
Операция метод, путь, бизнес-действие, владелец какое решение в предметной модели меняет вызов?
Поля направление, required, тип, nullability, допустимые значения что именно может прислать старая версия?
Успешный ответ статус, схема, поля, порядок при необходимости сможет ли клиент прочитать ответ без догадки?
Ошибки статус, type/код, безопасный detail, retry, логирование что сделает клиент при каждой ветви?
Совместимость что добавляется, что сохраняется, deprecated, дата пересмотра какое прежнее обещание остаётся рабочим?
Потребители сервис, версия, владелец, контакт кого нужно включить в проверку?
Проверка примеры, schema validation, contract/integration test, мониторинг какой тест упадёт при нарушении?
Откат условие остановки, действие, ответственный как вернуть безопасное состояние?

В эту карточку не стоит переписывать весь OpenAPI. Её задача — связать документ со способом работы. Спецификация отвечает на вопрос «какая форма допустима», а карточка — «кто обещает это поведение, как оно проверяется и как его изменять». Если нет потребителя или владельца, вероятно, договорённость существует только в памяти команды.

Проверять контракт до и после релиза

Спецификация полезна для ревью и генерации проверок, но она описывает намерение. Реализация может не соответствовать ей: middleware изменит ошибку, сериализатор удалит null, gateway перепишет статус, а клиент будет строже схемы. Поэтому нужен исполнимый пример на границе.

Contract testing, в том числе подход consumer-driven contracts, фиксирует ожидание потребителя и позволяет провайдеру проверить его на реализации. Это не требование использовать один конкретный продукт: документация Pact описывает сам принцип. Существенно другое: тест должен содержать наблюдаемое обещание, которое действительно важно потребителю, а не произвольный снимок всего JSON.

Минимальный набор перед релизом: проверить положительный сценарий; каждую документированную ошибку; старую версию клиента или её контрактный эквивалент; новый сценарий; границу прав; и метрику, которая покажет использование deprecated пути. Для опасных операций добавляют идемпотентность, гонки, таймаут зависимости и откат. Состав не универсален: он зависит от цены ошибки. Но тест без выбранного сценария не становится доказательством только от зелёного статуса CI.

После релиза контракт не заканчивается. Архитектор сверяет, что реальная телеметрия различает успешные и ошибочные ветви, а неизвестные ошибки не растворяются в одной метрике 500. Если потребитель всё ещё вызывает deprecated маршрут, это вход в разговор о миграции, а не повод внезапно удалить путь. Если наблюдаемость невозможна из-за требований к данным, измеряют обезличенный признак версии или код причины, а не тело запроса целиком.

Когда не стоит обещать совместимость

Иногда контракт меняется по существу: поле получает другое значение, доступ становится более строгим, операция становится асинхронной, или прежняя ошибка была небезопасной. Притворяться, что это «небольшое изменение», опаснее, чем создать явный маршрут миграции. Прямая версия, срок поддержки, примеры перевода, владельцы и проверка переключения обычно дешевле серии скрытых исключений.

Не стоит также превращать форму контракта в бюрократическую анкету. Если endpoint внутренний, один потребитель, а изменение ограничено и наблюдаемо, карточка может быть на одну страницу. Если endpoint публичный или участвует в финансовом, правовом, кадровом или доступном внешнему контуру процессе, одного YAML-файла почти наверняка мало: нужны оценка требований, модель ошибок и эксплуатационный план.

Тест понедельником

Выберите один endpoint, который меняется в ближайший спринт. До первой реализации выпишите: одно поле с инвариантом, один успешный ответ, две реальные ошибки, список потребителей, правило совместимости и тест, который увидит нарушение. Затем попросите владельца каждого потребителя подтвердить не документ, а конкретный пример запроса и ответа. Если пример нельзя согласовать за короткую встречу, проблема не в формате OpenAPI: контракт ещё не определён.

Если нужны независимое описание интеграционной границы, тестовые сценарии или план миграции, НТА помогает собрать их вместе с владельцами процессов и инженерными командами. См. также материалы о CORS-политике API, командной разработке BPMSoft с Git и допуске изменений в поставку.

Важное уведомление

Материал носит информационный характер и не является индивидуальной консультацией, проектной документацией, инструкцией по внедрению или гарантией результата. Применимость описанных подходов зависит от процессов, систем, данных, требований безопасности и иных условий конкретной организации. Перед внедрением или изменением ИТ‑систем необходимо самостоятельно оценить риски и привлечь профильных специалистов.

«В понедельник команда видит привычную задачу: сервис заказов должен передать сервису клиентов идентификатор клиента

— from project discussion
Updated · September 8, 2026

Similar challenge?
Let's map your context.

We will suggest the solutions and modules that fit a similar scenario.

/ What closed the task

Related materials

3 materials
/ Discussion

Be the first to leave a comment

To leave a comment or react, sign in or create an account.