open atlas
↑ К треку
API API · 04 · 01

OpenAPI: сделать контракт источником истины, а не доками

OpenAPI окупается, только когда спеку проверяют — генерируют клиентов, валидируют запросы и роняют CI на ломающем диффе. Спека, которую никто не проверяет, — это документация, которая врёт.

API Junior ◷ 16 min
Уровень
ОсновыJuniorMiddleSenior
Уже знаешь этот юнит? Пройди быструю проверку за минуту →

Бэкендер добавляет поле tenantId в payload создания заказа и помечает его required — изменение в одну строку, выкатил в пятницу. К понедельнику три мобильных клиента, партнёрская интеграция и внутренняя админка возвращают 422 на каждый заказ. Никто из них не слал tenantId; никто не знал, что оно есть. Контракт говорил, что поле опционально вчера и обязательно сегодня, и ничто в пайплайне этого не заметило. «Доки» были страницей в Confluence, отредактированной полгода назад. Настоящий контракт жил у кого-то в голове и поменялся, никому не сказав.

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

Спека — это контракт, иначе фикция

OpenAPI — это машиночитаемое описание твоего HTTP API: каждый путь, форма каждого запроса и ответа, каждый статус-код, каждая схема авторизации, записанные в YAML или JSON. Столько у большинства команд есть. Вопрос, который решает, помогает оно или вредит, лежит на уровень глубже: спека — это источник истины или описание истины, записанное где-то ещё?

Когда спека — побочный документ (написали один раз, выгрузили на портал доков, никогда не сверяли с работающим сервисом), она гниёт за месяцы. Код меняется, спека — нет, и теперь у тебя два контракта: тот, что сервер обеспечивает, и тот, что заявляет спека. Клиенты интегрируются по спеке, спека — фикция, и интеграция падает в проде. Это contract drift (молчаливое расхождение спеки и реализации), и это исход по умолчанию, пока что-то активно ему не мешает.

Фикс — не «писать доки лучше». Фикс — сделать спеку несущей: код генерируется из неё, запросы валидируются по ней, а её дифф гейтит каждый pull request. Контракт, который нельзя нарушить без падения билда, — это контракт. Всё остальное — страница в вики.

Spec-first vs code-first: где живёт истина

Спроси себя: контракт твоего API ревьюят до того, как написана первая строка хендлера, или он рождается из того, что инженер решил написать в пятницу? От ответа зависит, какую модель выбирать.

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

Spec-first (design-first — «спека раньше кода»): ты пишешь OpenAPI-документ руками, ревьюишь его как дизайн API и генерируешь серверные заглушки, типизированных клиентов и моки из него. Спека — источник истины; код ей соответствует. Фронтенд может строить против сгенерированного мока в тот же день, когда бэкенд только начал, потому что контракт существует раньше любой реализации.

Code-first («сначала код»): ты пишешь сервер с аннотациями или декораторами (@ApiProperty, type hints в FastAPI, springdoc), а инструмент генерирует спеку из работающего кода. Код — источник истины; спека — его отражение. Меньше церемоний, и спека не разойдётся с хендлером, из которого её извлекли, — но может разойтись с тем, чего ждали потребители, потому что никто не отревьюил контракт как контракт до выкатки.

Ни то ни другое не ошибка. Решение сеньора такое: spec-first, когда API потребляют несколько команд или внешние партнёры и контракт нужно ревьюить и держать стабильным; code-first, когда одна команда владеет обоими концами и скорость важнее предварительного дизайна. Что не опционально в любой модели — это enforcement: спека должна валидироваться и диффиться, как бы она ни родилась.

ИзмерениеSpec-firstCode-first
Источник истиныНаписанная руками спекаКод сервера + аннотации
Контракт ревьюят до кода?Да — это дизайн-артефактНет — рождается из реализации
Параллельная работа клиент/серверС первого дня (мок из спеки)После компиляции сервера
Риск driftСпека vs реализация (валидируй на edge)Контракт vs ожидания потребителя
Лучше всего дляПубличные API, много потребителейОдна команда владеет обоими концами

Что реально даёт OpenAPI 3.1

Спека отрабатывает не самим документом, а тулингом, который открывает. Важны четыре выигрыша:

  • Сгенерированные типизированные клиенты и серверные заглушки. Прогони генератор по спеке и получи SDK на TypeScript, Go, Swift, Kotlin — с методами, моделями, типами ошибок. Класс интеграционных багов («я слал userId, серверу нужен user_id») исчезает, потому что запрос больше никто не пишет руками. Команды, выкатывающие публичные API, генерируют SDK из спеки именно чтобы убить эту категорию целиком.
  • Валидация запросов на edge. Middleware валидирует входящие запросы по спеке до того, как они дойдут до хендлера. Битое тело отклоняется точным 400 с именем виноватого поля, а твоя бизнес-логика мусора не видит.
  • Доки, которые не разойдутся молча. Когда отрендеренные доки генерируются из той же спеки, что гейтит CI, «доки врут» перестаёт быть возможным — доки и есть контракт.
  • Мок-серверы. Инструмент отдаёт фейковые ответы прямо из примеров спеки, и потребители интегрируются раньше, чем появился бэкенд.

Вместе эти четыре выигрыша означают: спека отрабатывает не как документ, а как цепочка инструментов. Без CI-гейта первые три есть на бумаге, но ничто не останавливает ломающее изменение.

Конкретно OpenAPI 3.1 выравнивает язык схем с JSON Schema 2020-12, так что твои ключевые слова валидации — это настоящий JSON Schema, а не подмножество эпохи 3.0. Цена этой мощи: nullable: true больше нет — теперь пишешь type: ["string", "null"] — а exclusiveMinimum/exclusiveMaximum принимают число, а не булево. 3.1 ещё добавляет верхнеуровневый webhooks для документирования событий, которые твой API шлёт, а не только принимает. Многие инструменты по-прежнему дефолтят в 3.0, так что проверь поддержку генератора до апгрейда.

Почему это работает

Генераторы кода не магия — они кодируют мнения. Спека с рыхлыми схемами (additionalProperties: true, без массивов required, нетипизированные object-блобы) генерирует рыхлых, нашпигованных any клиентов, что убивает смысл. Дисциплина, которая делает генерацию ценной, — это плотные схемы: явные типы, явный required, переиспользуемые компоненты. Генератор честен ровно настолько, насколько честен контракт, который ты ему скармливаешь.

Детект ломающих изменений: гейт, который ловит пятничный деплой

Когда видишь PR, который трогает имя поля, тип или массив required, легко пропустить, стало оно required или нет — одно слово, а ломает каждого клиента, кто его не шлёт. У истории с tenantId один чистый фикс: инструмент, который диффит новую спеку против предыдущей версии и роняет PR на ломающем изменении. oasdiff (утилита для diff-анализа OpenAPI-спек) — частый выбор: он классифицирует 450+ категорий изменений и знает, какие ломающие. Добавление required-поля запроса, удаление поля ответа, которое клиент может читать, сужение типа, удаление значения enum, удаление эндпоинта — всё ломающее, всё ловится до мержа.

Вшитый в CI, чек туп и эффективен: пайплайн диффит спеку из PR против main, и если появляется ломающее изменение, билд красный, а инженер видит ровно какое поле сломалось — раньше любого клиента. Пятничный деплой не доходит до прода, потому что контракт обеспечивается тем же гейтом, что и тесты. Тонкость в 3.1: так как nullability переехала в массив типов, удаление "null" из type: ["string", "null"] регистрируется как смена типа, а не как смена nullable — дифф всё равно ловит это, просто по другому правилу, поэтому смотри на категорию, которую сообщает инструмент, а не только на количество.

Выбери лучший вариант

Публичный API потребляют мобильные приложения, которые ты не можешь принудительно обновить, и три партнёрские интеграции. Выбери стратегию enforcement.

Викторина

PR добавляет новое required-поле в тело запроса. Что должен сделать здоровый API-пайплайн?

Викторина

Твои написанные руками доки говорят, что поле опционально, но сервер теперь отклоняет запросы без него. Как это называется и что это предотвращает?

Расставь шаги по порядку

Расставь шаги spec-first пайплайна, который предотвращает drift:

  1. 1 Написать / обновить OpenAPI-спеку и отревьюить её как контракт
  2. 2 CI диффит спеку против предыдущей версии (oasdiff) и роняет билд на ломающем изменении
  3. 3 Сгенерировать типизированных клиентов, серверные заглушки и мок из спеки
  4. 4 Валидировать входящие запросы по спеке на edge до того, как отработает хендлер
  5. 5 Рендерить доки из той же спеки, чтобы они не могли противоречить контракту
Вспомните перед уходом
  1. 01
    Коллега говорит: «у нас уже есть OpenAPI-спека, значит, мы защищены от ломающих изменений». Почему это не так и чего на самом деле не хватает?
  2. 02
    Когда выбрать code-first вместо spec-first, и какой enforcement всё равно нужен в любом случае?
Итог

OpenAPI — это машиночитаемое описание HTTP API, но описание бесполезно, пока оно не источник истины. Сбойный режим — contract drift: написанные руками доки говорят одно, сервер обеспечивает другое, клиенты интегрируются против фикции, и изменение в одну строку вроде добавления required-поля ломает каждого потребителя в проде. Spec-first считает написанную руками спеку авторитетной и генерирует из неё клиентов, заглушки и моки; code-first генерирует спеку из аннотаций. В любом случае спеку надо обеспечивать — сгенерированные типизированные клиенты убивают целый класс багов с запросами вручную, валидация на edge отклоняет битый ввод до хендлера, а дифф ломающих изменений (oasdiff) роняет PR в тот момент, когда кто-то удаляет поле, сужает тип или добавляет required. OpenAPI 3.1 выравнивает схемы с JSON Schema 2020-12, выкидывает nullable ради массивов type и добавляет webhooks. Уплотни схемы переиспользуемыми $ref-компонентами, явным required и схемами авторизации, а затем дай пайплайну сделать контракт невозможным сломать молча. Теперь, когда встречаешь PR с изменением имени поля, типа или массива required, первый вопрос: запускался ли гейт ломающих изменений — и он зелёный?

Практика

Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.

вспомнитьприменитьуглубить0 из 5 завершено

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

Примени это

Примени этот урок в реальном проекте.

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources4
expand
  1. 01
  2. 02
  3. 03
  4. 04

Trademarks belong to their respective owners. Editorial reference only.