Обычно 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
- Пишем/правим OpenAPI-контракт — эндпоинты, схемы, ошибки, версии.
- Ревью контракта — обсуждаем дизайн на уровне YAML, до кода.
- Генерация —
openapi-generatorсоздаёт DTO + интерфейсы контроллеров для сервера и клиент для потребителя. - Реализация — контроллер реализует сгенерированный интерфейс; логика пишется внутри, контракт не переписывается руками.
- Проверка в 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 — из чего складывается хорошо спроектированный контракт.