← Все статьи

Contract-first, code-first и другие подходы к работе с API-контрактами

· 11 мин чтения

Чем отличаются code-first, contract-first, API-first и consumer-driven contracts, какие инструменты строятся вокруг спецификации OpenAPI и AsyncAPI, куда движется индустрия и как выбрать подход для своей интеграции.

Интеграция двух систем всегда держится на договорённости: какие адреса или каналы есть, какие данные в них ходят, какие ошибки возможны. Вопрос только в том, где эта договорённость живёт — в голове разработчика, в коде сервиса, в документе на вики или в машиночитаемом файле, из которого всё остальное получается автоматически. От ответа зависят сроки, количество переделок и то, насколько спокойно команды могут менять свои системы. Разберём основные подходы к работе с контрактами, чем они отличаются на практике и куда движется индустрия.

Что такое контракт API

Контракт — формальное описание интерфейса, достаточное, чтобы потребитель мог с ним работать, не заглядывая в реализацию. В нём фиксируются:

  • операции — адреса и методы HTTP, RPC-методы, каналы и сообщения брокера;
  • структуры данных — поля, типы, обязательность, форматы, ограничения (длина, диапазон, шаблон);
  • ошибки — коды, формат тела ошибки, условия возникновения;
  • нефункциональные договорённости — аутентификация, идемпотентность, пагинация, версии, лимиты.

Для каждого стиля взаимодействия есть свой стандарт описания:

СтильФормат контрактаЧто описывает
HTTP API (REST, JSON over HTTP)OpenAPI 3.xпути, методы, параметры, тела запросов и ответов, схемы безопасности
События и брокеры (Kafka, RabbitMQ, MQTT)AsyncAPI 2.x/3.xканалы, операции send/receive, сообщения и их заголовки
SOAP-сервисыWSDL + XSDоперации, сообщения, привязки к транспорту, XML-типы
gRPCProtocol Buffers (.proto)сервисы, RPC-методы, сообщения со строгой нумерацией полей
GraphQLSDL (схема GraphQL)типы, запросы, мутации, подписки
Отдельные структуры данныхJSON Schema, XSDформат документа, файла, сообщения

Важно различать контракт и документацию. Документация объясняет, как и зачем пользоваться API; контракт — это проверяемая спецификация, по которой можно сгенерировать код, валидировать запросы и находить несовместимые изменения. Хороший контракт включает описания и примеры и поэтому сам становится основой документации, но обратное неверно: страница на вики с таблицей полей контрактом не является — её нельзя проверить автоматически.

Code-first: сначала код

При подходе code-first (его также называют implementation-first или backend-first) разработчик пишет сервис, а описание API получается из кода: аннотации контроллеров и моделей превращаются в OpenAPI с помощью springdoc, Swashbuckle, FastAPI, NestJS Swagger и подобных инструментов. Для SOAP аналогично: JAX-WS генерирует WSDL по Java-классам.

Как выглядит процесс: аналитик пишет требования текстом, backend реализует сервис, после развёртывания на тестовом стенде появляется Swagger UI, по нему frontend и смежные команды начинают интеграцию.

Сильные стороны:

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

Слабые стороны:

  • потребители ждут: пока сервис не написан и не развёрнут, контракта нет, параллельная работа невозможна или идёт по устным договорённостям;
  • дизайн API следует за реализацией: в контракт протекают внутренние имена, структуры ORM-сущностей, особенности фреймворка (например, поле hibernateLazyInitializer или даты в разных форматах);
  • случайные несовместимые изменения: переименовали поле класса — изменился публичный API, и никто этого не заметил до падения у потребителя;
  • неполнота: генератор не знает про бизнес-ограничения, коды ошибок и примеры, если их не описать аннотациями — а аннотации обычно не пишут;
  • ревью API превращается в ревью кода: аналитик и архитектор должны читать Java или C#, чтобы увидеть, что поменялось в интерфейсе.

Contract-first: сначала контракт

При contract-first (design-first) контракт пишется до реализации и считается первичным артефактом. Код по обе стороны интеграции либо генерируется из него, либо проверяется на соответствие ему.

Типичный процесс:

  1. Аналитик или архитектор проектирует контракт: ресурсы, операции, модели, ошибки, примеры.
  2. Контракт проходит ревью вместе с потребителями — как пул-реквест в репозиторий, где лежит спецификация.
  3. Линтер проверяет соответствие гайдлайнам компании: именование, пагинация, формат ошибок, обязательные описания.
  4. Из контракта генерируются интерфейсы сервера, клиенты и типы; поднимается мок-сервер.
  5. Backend реализует сгенерированные интерфейсы, frontend и интеграторы параллельно работают с моком.
  6. В CI контракт сравнивается с предыдущей версией: несовместимые изменения блокируют сборку или требуют новой версии API.

Пример фрагмента OpenAPI, с которого начинается работа:

paths:
  /orders/{orderId}:
    get:
      operationId: getOrder
      summary: Заказ по идентификатору
      parameters:
        - name: orderId
          in: path
          required: true
          schema: { type: string, format: uuid }
      responses:
        '200':
          description: Заказ
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Order' }
        '404':
          description: Заказ не найден
          content:
            application/problem+json:
              schema: { $ref: '#/components/schemas/Problem' }

Из него openapi-generator создаст интерфейс контроллера, который останется только реализовать:

public interface OrdersApi {
    ResponseEntity<Order> getOrder(UUID orderId);
}

Если сервис вернёт не тот тип или кто-то удалит поле из модели, проект просто не скомпилируется. Так контракт перестаёт быть документом «для информации» и становится частью сборки.

Сильные стороны:

  • параллельная разработка: потребители начинают работу в день согласования контракта, а не после релиза сервиса;
  • API проектируется для потребителя, а не вырастает из внутренней модели;
  • дешёвые изменения: поправить YAML на этапе ревью стоит минуты, переделать реализованный сервис и двух клиентов — дни;
  • единый источник для кода, моков, тестов, документации и проверки обратной совместимости;
  • контракт читают все участники: аналитик, тестировщик, разработчики обеих сторон, архитектор, служба безопасности.

Слабые стороны и как с ними справляются:

  • порог входа: нужно знать OpenAPI или AsyncAPI и уметь проектировать API. Помогают визуальные редакторы, линтеры с понятными сообщениями и гайдлайны с примерами;
  • генераторы несовершенны: сложные конструкции (oneOf с дискриминатором, наследование, nullable) генерируются по-разному в разных языках. Решается настройкой генератора и договорённостью не использовать спорные конструкции без необходимости;
  • риск разойтись с реализацией, если код не генерируется, а пишется вручную «по контракту». Нужна проверка: генерация интерфейсов или контрактные тесты, которые валидируют реальные ответы по схеме;
  • больше формальностей на старте — для прототипа на один день это избыточно.

API-first: стратегия, а не техника

Термины contract-first и API-first часто используют как синонимы, но у них разный масштаб. Contract-first — инженерная практика для конкретной интеграции. API-first — организационный принцип: API считается продуктом, у которого есть потребители, жизненный цикл, версии, документация, метрики и владелец. Новая функциональность сначала проектируется как API, а пользовательский интерфейс — лишь один из его клиентов наряду с мобильным приложением, партнёрами и, всё чаще, ИИ-агентами.

На практике API-first почти всегда реализуется через contract-first: без машиночитаемого контракта невозможно ни вести каталог API, ни автоматически контролировать совместимость, ни публиковать портал разработчика.

Consumer-driven contracts: контракт от потребителя

В подходе consumer-driven contracts (CDC) ожидания формулирует каждый потребитель. Он пишет тест, который описывает, какие запросы он отправляет и какие поля ответа ему действительно нужны. Из теста получается файл-пакт, а поставщик в своём CI прогоняет все пакты потребителей против своей реализации. Самый известный инструмент — Pact; в мире Spring похожую задачу решает Spring Cloud Contract.

Главное преимущество — поставщик точно знает, какие поля кем используются, и может смело удалять то, что никому не нужно. CDC хорошо работает во внутренних микросервисных системах, где все потребители известны и готовы поддерживать тесты. Для публичных API и интеграций с внешними организациями подход неприменим: внешние потребители не будут присылать вам пакты.

CDC не заменяет contract-first, а дополняет его. Спецификация OpenAPI описывает всё, что поставщик обещает; пакты показывают, что из этого реально используется.

Frontend-first и мок-first

Встречается и обратная ситуация: интерфейс проектируют и делают первым, а нужный ему API выводят из экранов. Это оправдано для BFF (backend for frontend) — слоя, который существует ради одного клиента. Риск тот же, что у code-first, только с другой стороны: API получается заточенным под конкретный экран и плохо переиспользуется. Если команда идёт этим путём, полезно всё равно фиксировать результат в OpenAPI и работать с моком, а не с захардкоженными данными в коде интерфейса.

Сравнение подходов

КритерийCode-firstContract-firstConsumer-driven
Источник истиныкод поставщикафайл спецификациитесты потребителей
Когда потребитель может начатьпосле развёртывания сервисапосле согласования контрактасразу, на своих ожиданиях
Качество дизайна APIзависит от дисциплины разработчикапроектируется и ревьюится явноопределяется потребностями клиентов
Защита от поломокслабая без дополнительных провероксравнение версий спецификации, генерация кодасильная для известных потребителей
Подходит дляпрототипы, внутренние сервисы одной командымежкомандные и внешние интеграции, публичные APIмикросервисы внутри организации
Основная стоимостьпеределки после интеграциивремя на проектирование и инструментыподдержка тестов во всех командах

Инструменты вокруг контракта

Ценность contract-first раскрывается, когда вокруг спецификации выстроен конвейер. Типовой набор:

  • редакторы и визуализация — чтобы писать и читать спецификацию с подсветкой ошибок и предпросмотром документации;
  • линтеры — Spectral, Redocly CLI, Vacuum: правила оформления в виде кода, например «все операции имеют operationId», «идентификаторы — в формате uuid», «ошибки — в формате Problem Details»;
  • генераторы — openapi-generator (десятки языков и фреймворков), openapi-typescript для типов frontend, protoc для gRPC, генераторы AsyncAPI для моделей сообщений;
  • моки — Prism, WireMock, Microcks: сервер, отвечающий по примерам и схемам из спецификации;
  • проверка совместимости — oasdiff, openapi-diff, buf breaking для protobuf: сравнивают две версии и находят удалённые поля, ставшие обязательными параметры, изменённые типы;
  • контрактное тестирование — Schemathesis, Dredd: генерируют запросы по спецификации и проверяют, что реальный сервис отвечает по схеме.

Отдельно стоит договориться, что считается несовместимым изменением. Безопасно: добавить необязательное поле в запрос, новое поле в ответ, новую операцию. Несовместимо: удалить или переименовать поле, сделать поле обязательным, сузить допустимые значения, изменить тип или формат, поменять семантику кода ответа. Отдельный случай — добавление значения в перечисление в ответе: формально это расширение, но строгие клиенты, которые падают на незнакомом значении, сломаются. Такие правила стоит закрепить в гайдлайне и проверять автоматически.

Контракт и системный аналитик

В contract-first контракт становится основным артефактом аналитика в интеграционных задачах. Вместо таблицы «поле — тип — описание» в постановке аналитик отдаёт файл, который:

  • однозначно читается разработчиками обеих сторон и тестировщиками;
  • проверяется валидатором — опечатку в типе или забытое обязательное поле видно сразу;
  • хранится в репозитории с историей изменений и проходит ревью;
  • используется для генерации кода, поэтому не устаревает.

Это меняет и требования к навыкам: аналитику полезно понимать HTTP-семантику, принципы проектирования ресурсов, идемпотентность, пагинацию и версионирование, уметь читать JSON Schema и знать, как его конструкции превращаются в типы при генерации. Хорошая практика — описывать в контракте не только структуры, но и смысл: описания полей, примеры запросов и ответов, перечень ошибок с условиями возникновения. Именно эти части генератор из кода никогда не получит.

Тренды

Спецификации развиваются в сторону полной совместимости с JSON Schema и описания сценариев. OpenAPI 3.1 выровнял модель данных с JSON Schema 2020-12, поэтому одни и те же схемы можно использовать и в API, и для валидации документов. Версия 3.2 добавила описание потоковых ответов, иерархию тегов и метод QUERY. Спецификация Arazzo от той же OpenAPI Initiative описывает последовательности вызовов — многошаговые сценарии поверх нескольких операций. AsyncAPI 3.0 отделил каналы от операций и сделал описание событийных интеграций заметно понятнее.

Языки описания API. Писать большие спецификации на YAML неудобно, поэтому появляются компактные языки, компилируемые в OpenAPI, — например, TypeSpec. Они дают переиспользование, модули и проверку типов, а на выходе всё тот же стандартный контракт.

Управление API (governance) в CI. Линтинг по корпоративным правилам, проверка обратной совместимости и публикация в каталог API становятся обязательными шагами конвейера, как тесты и статический анализ кода.

Событийные интеграции догоняют HTTP. Реестры схем (Confluent Schema Registry, Apicurio) с проверкой совместимости Avro, Protobuf и JSON Schema для топиков Kafka — тот же contract-first, применённый к событиям. AsyncAPI связывает схемы сообщений с каналами и делает событийную архитектуру документируемой.

Контракты читают ИИ-агенты. Агентам и LLM-инструментам нужны машиночитаемые описания: из OpenAPI автоматически строятся инструменты для агентов, а протокол MCP описывает инструменты через JSON Schema. Чем точнее описания операций и полей в контракте, тем лучше агент выбирает нужный вызов и формирует запрос. Качественный контракт становится условием того, что API вообще можно будет использовать в автоматизации нового типа.

ИИ в проектировании. Черновик спецификации по описанию задачи, ревью на соответствие гайдлайнам, генерация примеров — задачи, которые всё чаще отдают ассистентам. Это ускоряет contract-first, но не отменяет ревью: ассистент уверенно предлагает правдоподобные, но не согласованные с потребителями решения.

Как выбрать подход

  • Интеграция между командами, организациями или с внешними партнёрами — contract-first без вариантов. Контракт — предмет договорённости, и он должен существовать до кода.
  • Публичный API или платформа — API-first: contract-first плюс линтинг, контроль совместимости, версионирование и портал документации.
  • Много внутренних микросервисов с известными потребителями — contract-first как основа, consumer-driven contracts как дополнительная страховка.
  • Прототип или внутренний сервис одной команды — code-first допустим. Но если сервис переживёт прототип, стоит выгрузить спецификацию, привести её в порядок и дальше вести как первичный артефакт.
  • Переход с code-first на contract-first — начните с выгрузки текущего OpenAPI из кода, включите сравнение версий в CI, чтобы зафиксировать текущее состояние, затем переносите генерацию интерфейсов сервис за сервисом.

Общее правило простое: чем больше сторон зависит от интерфейса и чем дороже его менять, тем раньше должен появиться контракт и тем больше вокруг него должно быть автоматики. Contract-first — не бюрократия, а способ перенести самые дорогие ошибки интеграции на этап, где их исправление стоит правки в одном файле.

Попробовать подход можно в онлайн-редакторе OpenAPI и редакторе AsyncAPI, а синтаксис спецификаций подробно разобран в документации по OpenAPI.