API-контракты
В этом топике разбираем, как формировать понятные и предсказуемые API-контракты без привязки к фреймворку. Мы пройдёмся по правилам именования endpoint-ов и выбору HTTP-глаголов, разберём пагинацию, батч-запросы, получение сущностей по ID, критерии выбора query - и path-параметров, когда использовать заголовки, как оформлять тело ответа (data-обёртки) и ошибочные ответы (поле _error
), а также какие статусы возвращать в разных ситуациях. Завершим кратким введением в OpenAPI и Swagger.
Базовые принципы и глаголы HTTP
Ресурсный подход (resource-oriented): endpoint описывает коллекцию или отдельный ресурс. Пример коллекции:
/users
, элемент коллекции:/users/{id}
.Глаголы определяются методом:
GET
— чтение (без побочных эффектов, идемпотентно, безопасно).POST
— создание или небезопасное действие (обычно не идемпотентно).PUT
— полная замена ресурса (идемпотентно).PATCH
— частичное обновление (может быть идемпотентным по контракту).DELETE
— удаление ресурса (идемпотентно: повторное удаление возвращает тот же результат состояния).
Именуйте пути существительными во множественном числе:
/orders
,/orders/{orderId}
, а не/getOrders
или/createOrder
.Гнездите под-ресурсы, когда есть жёстная иерархия:
/orders/{orderId}/items
.Версионирование: фиксируйте мажорную версию в пути (
/v1/...
) или через заголовок версии — главное, чтобы было стабильно и прозрачно.
Path vs Query vs Headers: что куда?
Path-параметры — часть
URI
, однозначно идентифицируют ресурс:/users/{id}
,/orders/{orderId}/items/{itemId}
.Query-параметры — фильтрация, поиск, сортировка, пагинация, не меняют идентичность ресурса:
/users?role=admin&sort=createdAt&order=desc
.Заголовки — сквозные технические аспекты: авторизация (
Authorization
),Idempotency-Key
,Accept
/Content-Type
, ключи трассировки, локаль (Accept-Language
).
Получение по ID
Классический шаблон: GET /{collection}/{id}
. Если ресурс не найден — возвращайте 404
. Для выборки нескольких конкретных ID используйте батч (см. ниже) или GET /users?ids=1,2,3
с ограничениями на длину URL
.
Пагинация: варианты и контракт
Есть три распространённых подхода: page/size, limit/offset, cursor. Выбирайте осознанно.
page/size — удобно для UI, но плохо при вставках/удалениях: элементы могут «прыгать» между страницами.
limit/offset — просто реализовать, но на больших оффсетах неэффективно в БД и подвержено сдвигу данных.
cursor — надёжно и эффективно для бесконечных списков: сервер возвращает
nextCursor
и/илиprevCursor
.
Батч-запросы (batch)
Батч уменьшает сетевые издержки и лимиты RPS, позволяя за один HTTP
-запрос обработать множество однородных операций. Два подхода:
Однотипный батч:
POST /users:batchGet
,POST /users:batchUpdate
.Смешанный батч:
POST /batch
с описанием разных операций (сложнее валидировать и трассировать).
Когда использовать заголовки
Аутентификация/Авторизация:
Authorization: Bearer ...
.Идемпотентность:
Idempotency-Key
для безопасного повтораPOST
.Контент-негациация:
Accept
,Content-Type
,Accept-Encoding
.Трассировка: корреляционные ID (
X-Request-Id
).Локализация:
Accept-Language
.
Data-обёртка и метаданные ответа
Единая структура ответа облегчает жизнь клиентам. Распространённый шаблон:
data — полезная нагрузка (объект или массив).
meta — технические метаданные: пагинация, трассировка, статистика.
Структура ошибок и поле _error
Ошибки должны быть машинно-парсабельными и человекочитаемыми. Если вы приняли формат с полем _error
, делайте его единообразным:
code — короткий машинный идентификатор.
message — безопасное описание для пользователя/разработчика.
details — массив конкретных нарушений.
requestId — для поддержки и трассировки.
Какие статус-коды когда использовать
200 OK
— успешное чтение/операция с телом ответа.201 Created
— создан ресурс; добавьтеLocation
на новый ресурс.202 Accepted
— операция принята асинхронно (проверьтеstatus
по отдельному ресурсу).204 No Content
— успех без тела (например,DELETE
).206 Partial Content
/207 Multi-Status
— частичный результат (стриминг/батч).304 Not Modified
— при условных запросах (If-None-Match
,If-Modified-Since
).400 Bad Request
— ошибка запроса (валидация, синтаксис).401 Unauthorized
— нет/невалидная авторизация.403 Forbidden
— прав не хватает.404 Not Found
— ресурс не найден.405 Method Not Allowed
— метод не поддержан для ресурса.409 Conflict
— конфликт версий, уникальности, состояния.410 Gone
— ресурс удалён и не вернётся.412 Precondition Failed
— нарушены условия (If-Match
и т.п.).415 Unsupported Media Type
— неверныйContent-Type
.422 Unprocessable Entity
— корректный JSON, но семантическая ошибка домена.429 Too Many Requests
— превышен лимит; добавьтеRetry-After
.500 Internal Server Error
— внутренняя ошибка сервера.503 Service Unavailable
— перегрузка/техработы; добавьтеRetry-After
.
Фильтрация, сортировка, поля ответа (query-параметры)
Частые параметры:
filter[<field>]=...
— фильтры (можно несколько).sort=createdAt
иorder=asc|desc
— сортировка.fields=...
— проекция полей (экономит трафик).include=...
— инклюды связанных представлений, если нужно.
Идемпотентность и повтор запросов
Для небезопасных операций POST
поддерживайте Idempotency-Key
в заголовке: сервер обязан обеспечить, что повтор того же ключа не создаст дубликат.
Примеры хороших и плохих путей
Хорошо:
/v1/users
,/v1/users/{id}
,/v1/users/{id}/roles
.Плохо:
/v1/getUsers
,/v1/createUser
,/v1/users/deleteById?id=...
.
OpenAPI и Swagger
OpenAPI — язык описания API (обычно YAML/JSON), позволяющий формализовать пути, параметры, схемы, ответы и ошибки. Swagger UI визуализирует и позволяет «пощёлкать» по спецификации, а Swagger Codegen
/OpenAPI Generator
— генерировать клиенты/стабы.
Практические рекомендации
Единый стиль путей и параметров по всему API — зафиксируйте в гайдлайне.
Всегда документируйте ответ с ошибкой. Клиенты должны знать структуру
_error
и наборcode
.Добавляйте примеры (
example
/examples
) в OpenAPI — это ускорит интеграцию студентов и новичков.Покрывайте контракты контрактными тестами (consumer-driven, например, Pact).
Фиксируйте ограничения: максимальные
limit
, поддерживаемые поля фильтров, сортировки, размеры батча.