← назад к разделу

Обычно API рождается из кода: написали контроллеры, а описание (если оно есть) сгенерировали потом. API-first переворачивает порядок: сначала договариваемся о контракте, и только потом пишем код по нему. Разберём, зачем так и как это устроено.

Что такое API-first простыми словами

API-first — подход, при котором контракт API проектируется первым, до реализации, и становится главным артефактом, по которому работают все стороны. Контракт — это машиночитаемое описание: какие есть эндпоинты, какие параметры и тела запросов, какие ответы и коды ошибок. Стандартный формат такого описания для REST — OpenAPI (YAML или JSON).

Короткая формула: сначала контракт, потом код. Контракт — это договор между теми, кто API предоставляет, и теми, кто его потребляет.

Противоположность — code-first: пишем код, а описание API получаем из него (аннотации, рефлексия). Оба варианта дают на выходе OpenAPI-документ, но порядок и источник правды разные.

Зачем это нужно

Когда контракт готов раньше кода, он сразу приносит пользу:

  • Параллельная работа. Фронтенд и бэкенд не ждут друг друга: оба берут согласованный контракт и работают одновременно. Фронт поднимает мок (заглушку) по спецификации и пишет интерфейс, пока бэкенд реализует логику.
  • Единый источник правды. Контракт один, и он машиночитаемый. Не бывает ситуации «в коде одно, в документации другое» — документация генерируется из того же контракта.
  • Генерация кода. Из OpenAPI генерируются DTO, интерфейсы контроллеров (server stubs) и клиенты. Меньше ручного шаблонного кода и меньше расхождений между сторонами.
  • Ранний разговор о дизайне. Контракт легко прочитать и обсудить на ревью до того, как написана хоть строчка реализации. Поправить YAML дешевле, чем переписывать готовый код.
  • Проверка совместимости. Контракт можно автоматически сравнивать между версиями и ловить ломающие изменения в CI.

Contract-first и code-first

Оба пути ведут к OpenAPI-документу, но по-разному.

Contract-first — источник правды это OpenAPI YAML. Сначала пишем спецификацию руками, затем openapi-generator создаёт из неё DTO и интерфейсы. Контроллер реализует сгенерированный интерфейс (implements <Tag>Api), а валидационные ограничения (@NotNull, форматы, длины) попадают в DTO автоматически из YAML.

# фрагмент контракта: контракт первичен
paths:
  /orders/{id}:
    get:
      operationId: getOrder
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string, format: uuid }
      responses:
        "200":
          description: Заказ
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Order" }

Code-first — источник правды это код: классы DTO и аннотации, из которых описание API генерируется автоматически (в Spring это springdoc, в FastAPI — модели Pydantic, в Go — struct-теги). Быстрее на старте, но контракт здесь — следствие кода, а не договор.

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

Как выглядит процесс contract-first

  1. Пишем/правим OpenAPI-контракт — эндпоинты, схемы, ошибки, версии.
  2. Ревью контракта — обсуждаем дизайн на уровне YAML, до кода.
  3. Генерацияopenapi-generator создаёт DTO + интерфейсы контроллеров для сервера и клиент для потребителя.
  4. Реализация — контроллер реализует сгенерированный интерфейс; логика пишется внутри, контракт не переписывается руками.
  5. Проверка в CI — линт контракта и проверка обратной совместимости с предыдущей версией.

Важное правило команды: правки контракта идут в YAML, а не в сгенерированный код. Сгенерированные файлы перезаписываются при каждой сборке — менять их руками бессмысленно.

Моки и параллельная работа

Главный практический выигрыш API-first — мок прямо из контракта. Инструменты (например, Prism) поднимают фейковый сервер по OpenAPI-файлу: он отвечает примерами из спецификации. Фронтенд и потребители-сервисы начинают интеграцию сразу, не дожидаясь готового бэкенда. Когда реальный сервис готов, переключаются с мока на него — контракт-то один и тот же.

Версионирование и эволюция контракта

Контракт живёт долго, и его нужно менять не ломая потребителей. Базовые правила: добавлять необязательные поля можно, удалять или переименовывать существующие — это ломающее изменение, которое требует новой версии. Контракт — удобное место, где это видно: diff двух YAML сразу показывает, что изменилось. Подробнее про версии — в отдельной статье ниже.

Когда API-first избыточен

Подход не бесплатный: контракт нужно поддерживать, а генерация добавляет шаг в сборку. Для одноразового прототипа или крошечного внутреннего эндпоинта это перебор — там быстрее code-first. API-first окупается, когда API переживёт не один спринт, его потребляет больше одной команды или он публичный.

Коротко

  • API-first = сначала контракт (OpenAPI), потом код по нему.
  • Даёт параллельную работу фронта и бэкенда, единый источник правды, генерацию кода и моки из спецификации.
  • Contract-first — источник правды YAML (генерируем код из него); code-first — источник правды код (генерируем описание из него).
  • Contract-first хорош для публичных API и нескольких команд; code-first — для маленьких внутренних сервисов.
  • Правки контракта — в YAML, не в сгенерированный код.
  • Версии контракта: добавлять необязательное можно, удалять/переименовывать — ломающее изменение.

Что почитать дальше

  • OpenAPI: метаданные и типичные ошибки в REST — сам формат контракта: operationId, tags, параметры и частые ошибки.
  • Версионирование REST API — как менять контракт, не ломая потребителей.
  • URL и ресурсы REST — из чего складывается хорошо спроектированный контракт.