Модуль 1 · API как контракт

Справочник «Стандартизация HTTP API» · модуль 1 · API как контракт

Глава 1.1

1.1

Контракт
и проверка соответствия

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

Категория
Основы стандартизации API
Уровень
Начальный
Связанные главы
1.2 Один обмен · 5.2 Breaking changes · 6.1 API Style Guide
Зачем это знать
Чтобы отличать «сервер отвечает» от «API можно отдать другой команде»

§1Что сервер обещает клиенту

Слово «контракт» в разработке означает не юридический документ, а набор обещаний, которые одна программа даёт другой. Разберём их на учебном сервисе. Service Desk API — сервис регистрации обращений в поддержку: сотрудник сообщает «не работает VPN», специалист меняет статус обращения, пока проблема не решена.

Обещания этого сервиса можно выписать по пунктам, и каждое из них проверяемо:

  • по адресу /api/v1/tickets методом GET вернётся страница обращений;
  • в каждом обращении будут поля id, title, description, status, created_at, updated_at;
  • status примет одно из трёх значений: open, in_progress, closed;
  • создание выполняется методом POST на тот же адрес и отвечает кодом 201, а адрес нового обращения приходит в заголовке Location;
  • если обращения с таким идентификатором нет, придёт код 404 и тело в формате application/problem+json;
  • операции записи требуют токен; без него — 401, с токеном только для чтения — 403.

Пока сервис выполняет эти обещания, любая программа, написанная по ним, работает. Если сервис меняет хотя бы одно обещание без предупреждения, перестаёт работать программа клиента, а не сам сервис. Автор изменения об этой программе может не знать и доступа к ней не имеет.

ОпределениеКонтракт API

Совокупность видимых клиенту обещаний сервиса: адреса ресурсов, методы, параметры, структура запроса и ответа, коды состояния, формат ошибок, требования аутентификации и правила изменения всего перечисленного.

У контракта две стороны и один посредник — протокол HTTP. Клиент не видит ни языка сервера, ни его базы данных, ни числа запущенных экземпляров. Он видит адреса, методы, коды и JSON. Стандартизируют именно этот видимый слой: он сохраняется при переписывании сервера на другой язык и при смене базы данных.

§2Почему «работает» ещё не значит «готово»

Возьмём изменение, которое выглядит безобидно. Разработчик решил, что имя поля updated_at неудачное, и переименовал его в modified_at. Переименование он выполнил аккуратно: поправил схему ответа, хранилище и все тесты.

app/schemas.py · было
class Ticket(BaseModel):
    id: UUID
    title: str
    status: TicketStatus
    created_at: datetime
    updated_at: datetime
app/schemas.py · стало
class Ticket(BaseModel):
    id: UUID
    title: str
    status: TicketStatus
    created_at: datetime
    modified_at: datetime

Запускаем проверки проекта. Все 31 тест проходят: сервис отвечает 200, отдаёт обращения, ошибки возвращает в прежнем формате. Со стороны кода изменение выполнено правильно.

вывод pytest на этой ветке
...............................                                          [100%]
31 passed in 0.08s

Теперь посмотрим на то же изменение со стороны контракта. Инструмент oasdiff сравнивает описание API до и после изменения и перечисляет то, что сломает существующих клиентов. Вот его настоящий вывод на этой ветке:

вывод oasdiff в pull request № 6 учебного проекта
in API GET /api/v1/tickets removed the required property `items/items/updated_at`
    from the response with the `200` status
in API POST /api/v1/tickets removed the required property `updated_at`
    from the response with the `201` status
in API GET /api/v1/tickets/open removed the required property `items/items/updated_at`
    from the response with the `200` status
in API GET /api/v1/tickets/{ticket_id} removed the required property `updated_at`
    from the response with the `200` status
in API PATCH /api/v1/tickets/{ticket_id} removed the required property `updated_at`
    from the response with the `200` status

Пять операций перестали отдавать поле, которое обещали. Мобильное приложение, написанное полгода назад, ищет в ответе updated_at, не находит и показывает пустую дату. Сервер при этом «работает»: отвечает 200 и не пишет ни одной ошибки в лог.

Прогон GitHub Actions на pull request: job contract зелёный, job breaking-changes красный, пять аннотаций oasdiff о пропавшем поле updated_at
GitHub Actions, pull request № 6 учебного проекта. Слева зелёный contract — тесты и линтер прошли. Красный breaking-changes — сравнение контрактов нашло пять нарушений. Изменение не попадёт в main: ветка защищена, обе проверки обязательны.

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

§3Что именно стандартизируют

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

Элемент контрактаЧто фиксируемЧто ломается без правила
Адреса ресурсовсуществительные во множественном числе, версия в начале путиклиент не угадывает третий адрес, зная первые два
HTTP-методыкакой метод для какой операцииудаление приходит методом POST, и посредник его повторяет
Параметрычто в пути, что в query, какие значения допустимыидентификатор ищут то в пути, то в параметре
Структура JSONстиль имён, форматы даты и времениразбор ломается на втором ответе: created_at и createdAt
Коды состояниякакой код описывает какой исходклиент разбирает текст ошибки вместо кода
Формат ошибокединая структура тела ошибкина каждый endpoint пишется свой обработчик
Версионированиегде записана версия и когда она меняетсянесовместимое изменение выходит без предупреждения
Документациячто описано, где лежит, как обновляетсяREADME расходится с кодом и вводит в заблуждение
Безопасностькакие операции требуют токена и какие праваоперация записи открыта всем, и это заметят не сразу
Правила изменениячто считать breaking changeкаждый решает сам, и клиенты ломаются по очереди
Проверкачем и когда правила применяютсяправила существуют на бумаге и не выполняются

Последняя строка таблицы относится не к самому API, а к порядку работы с правилами. Ей посвящены модули 6 и 7.

§4Стандартизация и проверка соответствия

Дисциплина называется «стандартизация, сертификация и техническое документоведение». Первые два слова обозначают разные действия, и различать их нужно с самого начала.

действие 1Стандартизация
  • отвечает на вопрос «как должно быть»;
  • результат — документ с правилами;
  • в учебном проекте это docs/API_STYLE_GUIDE.md;
  • правила формулирует команда, опираясь на стандарты и опыт компаний.
действие 2Проверка соответствия
  • отвечает на вопрос «так ли это сейчас»;
  • результат — отчёт: прошло или нет;
  • в учебном проекте это тесты, линтер и проверки в CI;
  • выполняется при каждом изменении, без участия человека.

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

Как это выглядит в работе

Правило «у каждой операции должен быть operationId» записано в API_STYLE_GUIDE.md — это стандартизация. Проверку выполняет линтер Spectral и тест test_every_operation_has_operation_id: они падают, если правило нарушено. Разработчик узнаёт об этом через минуту после push, а не на review через два дня.

§5Почему у API нет сертификата

В технике сертификация — процедура, в которой независимая сторона подтверждает соответствие продукта требованиям стандарта и выдаёт документ. Так сертифицируют кабель, детское автокресло, медицинский прибор. Продукт испытывают в аккредитованной лаборатории, а сертификат можно предъявить покупателю.

Для обычного REST API такой процедуры не существует. Нет органа, который выдаст вашему сервису документ «API соответствует правилам», потому что нет единого обязательного стандарта, которому такой сервис должен соответствовать. Есть стандарты протокола (их издаёт IETF), есть формат описания (его развивает OpenAPI Initiative), есть правила отдельных компаний — и они друг другу не тождественны.

Осторожно с формулировками

Фраза «наш API сертифицирован» без указания, кем и по какому стандарту, не означает ничего. В отчёте по практической работе пишите иначе: «API соответствует правилам docs/API_STYLE_GUIDE.md, проверка выполняется pytest, Spectral и oasdiff в GitHub Actions; последний прогон — ссылка».

Отдельные области — исключение. Платёжные сервисы проходят аудит PCI DSS, медицинские системы — проверки по локальному законодательству, государственные сервисы — требования регуляторов. Но это сертификация организации и процессов, а не «правильности API» как таковой.

§6Чем проверяют в этой книге

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

pytestПоведение операций и правила контракта: коды ответов, заголовки, наличие operationId, совпадение выгруженного описания с кодом.31 тест
SpectralПравила стиля описания: контакты и описание в info, теги и operationId у операций, snake_case в схемах, application/problem+json у ошибок.линтер
oasdiffСравнение описания в pull request с описанием в main: находит удалённые и переименованные поля, новые обязательные параметры, изменение типов.breaking changes
export --checkПроверка, что openapi/openapi.yaml в репозитории совпадает с тем, что генерирует код. Разошлись — сборка падает.против drift
CODEOWNERSИзменение контракта требует review владельца: GitHub запрашивает его автоматически, защита ветки делает обязательным.человек

Первые четыре проверки выполняют программы, последнюю — человек на ревью. Автоматически проверяется не всё: понятно ли названо поле, уместен ли выбранный код ответа, не раскрывает ли сообщение об ошибке лишнего. Чем больше правил переведено в тесты и правила линтера, тем меньше таких вопросов остаётся человеку.

Корень репозитория api-standard-template на GitHub: папки .github, app, docs, openapi, scripts, tests и файлы .spectral.yaml, CHANGELOG.md, LICENSE, README.md
Репозиторий учебного проекта. Кода в нём меньше, чем правил и проверок: docs/ — правила и источники, openapi/ — выгруженный контракт, tests/ — проверки контракта, .github/ — CI, шаблоны и владельцы кода.

§7Частые ошибки

«API работает, значит, его можно отдавать»Работающий код отвечает на запросы сегодня и в одной программе. Перед передачей проверяют другое: описан ли контракт, единообразны ли ошибки, что произойдёт с клиентами при следующем изменении.
Правила только на словахДоговорённость, которую нельзя показать и проверить, живёт до первого срочного релиза. Правило записывают в файл и привязывают к проверке.
«Мы сертифицируем API»Органа сертификации для обычного REST API нет. Говорить следует о соответствии собственному стандарту и о том, чем это соответствие проверено.
Переименование «без последствий»Любое переименование поля ответа — breaking change, даже если все тесты зелёные. Такие изменения выпускают в новой мажорной версии.

§8Проверьте себя

  1. Назовите три обещания, которые Service Desk API даёт клиенту, и скажите, чем каждое проверяется.
  2. Почему переименование updated_at в modified_at не ломает тесты, но ломает клиентов?
  3. Чем стандартизация отличается от проверки соответствия? Приведите по одному примеру из учебного проекта.
  4. Что ответить на вопрос «ваш API сертифицирован?» так, чтобы ответ был правдой?
  5. Какие проверки контракта человек выполняет сам и почему их нельзя поручить линтеру?
Контрактадреса · методы · параметрытело · коды · ошибкиверсия · безопасность
Стандартизацияdocs/API_STYLE_GUIDE.mdMUST / SHOULD / MAYисточник у каждого правила
Проверкаpytest · spectraloasdiff · export --checkCODEOWNERS + CI
Формулировкане «сертифицирован»а «соответствует правилампроекта, проверка — ссылка»

Справочник «Стандартизация HTTP API» · модуль 1 · глава 1.1Макаров Максим Николаевич

Кадр