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

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

Глава 1.4

1.4

Учебный стенд
Service Desk API

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

Категория
Рабочее место
Уровень
Начальный
Что понадобится
Python 3.12, терминал, браузер; Node.js — только для линтера в модуле 6
Результат
Запущенный сервис, открытый Swagger UI и первый успешный запрос

§1Что это за сервис

Service Desk API — учебный сервис регистрации обращений в службу поддержки. Сотрудник сообщает о проблеме: «не работает VPN», «принтер в 312 печатает пустые листы». Каждое сообщение становится обращением с заголовком, описанием, статусом и временем создания. Специалист поддержки меняет статус: open → in_progress → closed.

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

Данные живут в памяти процесса: после перезапуска остаются три демонстрационных обращения. База данных здесь не нужна: контракт API от способа хранения не зависит.

Где лежит проект

Репозиторий: github.com/MaximBytecamp/api-standard-template. Он помечен как шаблон: кнопка Use this template создаёт вашу копию — она понадобится в модуле 8. Копия проекта есть и в репозитории курса: standartizaciya-sertifikaciya-i-dokumentovedenie/api-standard-template/.

§2Запуск за четыре шага

  1. Получите проектСкачайте архив по ссылке выше (Code → Download ZIP) или выполните git clone https://github.com/MaximBytecamp/api-standard-template.git. Откройте папку в терминале.
  2. Создайте окружениеВиртуальное окружение изолирует библиотеки проекта от системного Python.
  3. Установите зависимостиВерсии закреплены в requirements.txt: у всех участников курса будет одинаковое поведение.
  4. Запустите серверФлаг --reload перезапускает сервис при сохранении файла — это пригодится, когда вы начнёте менять код.
терминал · шаги 2–4
python -m venv .venv
source .venv/bin/activate        # Windows: .venv\Scripts\activate
pip install -r requirements.txt
uvicorn app.main:app --reload
вывод uvicorn
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     Started reloader process using WatchFiles
INFO:     Application startup complete.

Откройте в браузере http://127.0.0.1:8000/docs. Должна появиться страница Swagger UI: название сервиса, его версия и список операций, сгруппированных тегом Tickets.

Swagger UI учебного проекта: заголовок Service Desk API, версия, ссылка на openapi.json, выбор сервера, группа Tickets и раскрытая операция GET /api/v1/tickets
Swagger UI после запуска. В шапке — название и версия контракта, ссылка на /openapi.json и кнопка Authorize. Ниже — группа Tickets и пять операций. Кадр снят на версии 1.0.0; в 1.1.0 у операций появились дополнительные заголовки.

§3Что внутри репозитория

Кода в проекте меньше, чем правил и проверок. Репозиторий устроен так намеренно: в этой дисциплине разбирается не реализация сервиса, а правила, по которым он описан и проверяется.

api-standard-template/

app/сервис: main.py — метаданные и сборка описания, schemas.py — схемы запросов и ответов, problems.py — формат ошибок, api/v1/endpoints/tickets.py — пять операций
docs/API_STYLE_GUIDE.md — правила проекта, STANDARDS.md — источник каждого правила с цитатой и переводом, VERSIONING.md, ERRORS.md, SECURITY.md, QUICKSTART.md, adr/
openapi/выгруженный контракт: openapi.json и openapi.yaml — то, что читают инструменты и другие команды
tests/test_tickets.py — поведение операций, test_http_features.py — заголовки контракта, test_contract.py — правила описания
scripts/export_openapi.py — выгрузка и сверка контракта с кодом, build_docs.py — страницы видов ошибок
.github/workflows/ci.yml — проверки, workflows/docs.yml — публикация документации, CODEOWNERS, шаблон pull request, формы issue
.spectral.yamlправила линтера: встроенный набор для OpenAPI плюс три собственных правила проекта
CHANGELOG.mdистория изменений контракта для потребителей API

К каждой из этих папок мы вернёмся: openapi/ — в модуле 3, docs/ — в модуле 6, .github/ — в модуле 7.

§4Пять операций и два токена

Чтение открыто всем. Операции записи требуют токен — так в учебном сервисе показана разница между «сервер не знает, кто вы» и «сервер знает и не разрешает».

ТокенЧтениеСоздание, изменение, удаление
не переданда401 Unauthorized
demo-tokenдада
viewer-tokenда403 Forbidden

В Swagger UI токен вводится один раз: кнопка Authorize в шапке, поле Value, затем Authorize и Close. После этого интерфейс сам добавляет заголовок Authorization к запросам, у которых в контракте объявлена схема безопасности — у них в списке стоит значок замка.

Окно Available authorizations в Swagger UI: схема BearerAuth типа http, поле Value с введённым demo-token, кнопки Authorize и Close
Окно Authorize. Схема называется BearerAuth, её тип — http, механизм — bearer. Это не настройка интерфейса, а часть описания API: Swagger UI построил окно по разделу components.securitySchemes.
Учебное упрощение

Два статических токена в переменных окружения — учебное упрощение. В рабочем контуре токен выдаёт сервер авторизации по OAuth 2.0 или OpenID Connect, а сервис проверяет подпись и срок действия. Но разницу между 401 и 403 удобнее увидеть на простом стенде.

§5Первый запрос

Прочитаем список обращений. Чтение токена не требует, поэтому команда короткая:

терминал
curl -i "http://127.0.0.1:8000/api/v1/tickets?limit=2"
ответ
HTTP/1.1 200 OK
content-type: application/json
x-request-id: 0b6f3c1e-2a9d-4a4f-9d7e-3f4a1c2b5d6e

{"items": [ … ], "total": 3, "limit": 2, "offset": 0}

Теперь создадим обращение. Здесь уже нужен токен, а вместе с ним — заголовок формата и тело:

терминал
curl -i -X POST http://127.0.0.1:8000/api/v1/tickets \
  -H "Authorization: Bearer demo-token" \
  -H "Content-Type: application/json" \
  -d '{"title": "Не работает VPN", "description": "После обновления клиента"}'

В ответе придёт код 201, заголовок location с адресом нового обращения и его тело. Попробуйте повторить ту же команду без строки с Authorization — сервер ответит 401 и телом в формате Problem Details. С токеном viewer-token ответ будет 403: клиент известен, но записывать ему нельзя.

Три ответа — 201, 401, 403 — на один и тот же запрос с разными токенами. Это и есть контракт в действии: поведение предсказуемо и описано заранее.

§6Что проект проверяет о себе сам

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

терминал
pytest
python scripts/export_openapi.py --check
npx @stoplight/spectral-cli lint openapi/openapi.yaml
вывод
...............................                                          [100%]
31 passed in 0.08s

# файлы описания совпадают с кодом — команда завершилась без сообщений

No results with a severity of 'warn' or higher found!
pytestПроверяет поведение операций и правила контракта: коды ответов, заголовки, наличие operationId, формат ошибок.31 тест
export --checkСверяет openapi/openapi.yaml с описанием, которое генерирует код. Разошлись — значит, кто-то изменил код и забыл выгрузить контракт.против расхождения
SpectralПроверяет стиль описания по правилам .spectral.yaml: контакты, теги, snake_case, формат ошибок, отсутствие глаголов в адресах.линтер

Те же три шага выполняет GitHub Actions при каждом изменении — об этом модуль 6. Запуск их локально до push экономит время: ошибку видно сразу, а не через две минуты в интерфейсе GitHub.

§7Если что-то не работает

Ниже — проблемы, которые встречаются чаще всего, и признак, по которому каждую из них узнают.

Что видноПричинаЧто сделать
Address already in use при запускепорт 8000 занят другим процессомзапустить на другом порту: uvicorn app.main:app --port 8001
ModuleNotFoundError: No module named 'app'сервер запущен не из папки проектаперейти в папку api-standard-template или добавить --app-dir
/docs открывается, /redoc пустойбраузер не смог загрузить скрипт ReDoc с CDNпроверить подключение к сети; в проекте версия ReDoc закреплена явно
401 на созданиене передан заголовок Authorizationв Swagger UI нажать Authorize, в curl добавить -H "Authorization: Bearer demo-token"
422 на созданиетело не соответствует схеме: нет title, он короче трёх символов или передано лишнее полепосмотреть массив errors в теле ответа: там указано поле и причина
Изменения в коде не виднысервер запущен без --reloadперезапустить с флагом или вручную
Правило разбора

Сначала смотрите на код ответа, потом на тело, потом в лог сервера. Код 4xx означает, что исправлять нужно запрос, 5xx — что смотреть надо в лог сервера. Идентификатор из заголовка x-request-id есть и в ответе, и в записи лога: по нему находят нужный запрос.

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

Установка библиотек без окруженияБез venv версии библиотек смешиваются с другими проектами, и поведение перестаёт совпадать с описанным в справочнике.
Запуск не из папки проектаuvicorn ищет пакет app относительно текущей папки. Сообщение ModuleNotFoundError означает именно это, а не поломку проекта.
Правка кода без выгрузки контрактаПосле изменения схем нужно выполнить python scripts/export_openapi.py, иначе проверка --check найдёт расхождение.
Считать данные постояннымиОбращения хранятся в памяти процесса. Перезапуск возвращает три демонстрационных обращения — это ожидаемо.

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

  1. Запустите сервис и откройте /docs. Какая версия контракта указана в шапке?
  2. Создайте обращение из Swagger UI и найдите в ответе заголовок location. Откройте этот адрес в браузере.
  3. Повторите создание без токена и с токеном viewer-token. Какие коды вернул сервер и чем отличаются тела ответов?
  4. Запустите три проверки проекта. Что именно сообщает каждая из них?
  5. Измените в app/main.py название сервиса, обновите /docs, затем выполните python scripts/export_openapi.py --check. Что произойдёт и почему?
Запускpython -m venv .venvpip install -r requirements.txtuvicorn app.main:app --reload
Адреса/docs — Swagger UI/redoc — ReDoc/openapi.json — контракт
Токеныdemo-token — записьviewer-token — только чтение
Проверкиpytestexport_openapi.py --checkspectral lint openapi/openapi.yaml

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

Кадр