Раздел 16 · Архитектура и решения

API contract governance: контракт как закон для двух команд

senior~35 мин

без входаВойди, чтобы отмечать прогресс, вести заметки и решать задачи в редакторе. войти

API contract governance: контракт как закон для двух команд

Сцена · поле переименовали в чате

Утро, прод, у половины пользователей пустой экран вместо списка заказов. Фронт ищет свой баг и не находит. К обеду выясняется: бэкенд вчера выкатил «безобидный рефакторинг», поле total в ответе стало totalCount. В чате даже было предупреждение, три дня назад, между мемом и обсуждением обеда.

Разбор полётов упирается в стену. Бэкенд прав: они предупредили и улучшили имя. Фронт прав: никто не подписывался читать весь чат. Виноватых нет, потому что нет закона, который нарушили. Есть привычка «обычно оно так работает», а привычка не версионируется и не проверяется в CI.

Это последний урок раздела, и он про самый нагруженный документ из всех: контракт между двумя командами. Здесь сходится всё, что мы собирали: спека из урока 05, API-запись из урока 06, CI из урока 07 и postmortem из урока 08.

Карта урока · пять принципов

Контракт работает, когда выполняются пять условий. Они выросли из реальной практики, и каждое закрывает конкретный способ устроить сцену выше:

  1. Контракт обязывает обе стороны.
  2. Schema-first: сначала схема, потом код.
  3. Контракт описывает поведение, а не реализацию.
  4. У каждого API есть владелец, и он назначен по типу API.
  5. Автоматизация вместо доверия: что можно проверить в CI, проверяет CI.

Чем контракт между командами отличается от типа в коде

Мы уже встречали контракт в уроке про принципы архитектуры: форма плюс поведение плюс граница. Внутри одного репозитория его охраняет компилятор: переименовал поле, сборка упала, ты же её и починил. Цена нарушения секунды.

Контракт между командами живёт в другой весовой категории. Стороны деплоятся независимо, компилятора между ними нет, а цена нарушения измеряется чужим простоем. Поэтому контракт здесь перестаёт быть типом и становится законом: документом с владельцем, процедурой изменения и машинной проверкой. Governance в названии урока это ровно эти три вещи.

Принцип 1 · контракт обязывает обе стороны

Никто не меняет контракт в одностороннем порядке. Ни бэкенд «улучшая имена», ни фронт, начавший зависеть от поля, которое в схеме помечено как внутреннее.

Из этого следуют две процедуры:

  • Breaking change не запрещён, он оформляется: предложение, окно депрекации, migration plan, согласие потребителей. Удаление поля это проект, а не коммит.
  • Нарушение контракта это инцидент. Не «ну бывает», а полноценный incident record и blameless postmortem из прошлого урока: что в процессе позволило выкатить ломающее изменение без процедуры.

Жёсткость второго пункта не ради наказания. Пока нарушение контракта стоит дёшево, его будут совершать; когда оно автоматически порождает разбор, в процессе быстро появляются недостающие проверки.

Принцип 2 · schema-first

Схема пишется до кода. Не «код написали, схему сгенерировали для документации», а наоборот: schema-first: OpenAPI или Protobuf файл предлагается, ревьюится, утверждается, и только после этого стороны пишут реализацию.

Что это даёт:

  • Ревью контракта отдельно от кода. Спор «нужно ли это поле» происходит над диффом схемы в десять строк, а не над PR на тысячу.
  • Параллельная работа. Утверждённая схема позволяет фронту строить UI на моках, пока бэкенд пишет реализацию. Очередность исчезает.
  • Кодген обеих сторон. Типы клиента и сервера, валидаторы и моки генерируются из одного исходника, расхождение становится технически невозможным. Инструменты кодгена разбираем в уроке про GraphQL, gRPC и кодоген.

Schema-first это тот же ход, что RFC перед решением в уроке 06: спор переносится в дешёвую фазу, пока ошибка стоит десять строк текста, а не неделю переписывания.

Принцип 3 · поведение, не реализация

Контракт описывает, что обещано наблюдателю, и молчит обо всём остальном. Две типовые утечки реализации в контракт:

  • Бэкенд зеркалит базу. Ответ API повторяет таблицу: user_id, created_ts, флаги, которые имеют смысл только внутри. Теперь любая миграция схемы БД это breaking change для клиентов. База должна уметь меняться, не двигая контракт.
  • Фронт диктует внутренности. «Сделайте нам эндпоинт, который ходит в эти три таблицы и возвращает join» это требование к реализации. Корректная форма: «нам нужны такие-то данные на такой-то экран с такой задержкой», а как бэкенд их соберёт, его дело.

Проверка на утечку: можно ли полностью переписать реализацию (сменить базу, язык, разбить сервис надвое), не тронув контракт? Если нет, в контракте торчит реализация.

дальше после входа

Урок целиком открыт всем, кто вошёл. Войди по своему email, чтобы читать дальше.

Вход пока по приглашению. Если хочешь учиться, напиши Vassiliy.