Читать книгу Full stack Developer - Группа авторов - Страница 10
Раздел II. Продукт и требования, единые для всех реализаций.
Глава 8. API Contract (OpenAPI) – пишем до кода
ОглавлениеЭта глава – про дисциплину, которая экономит недели: сначала описываем API как договор, а потом реализуем.
OpenAPI – это формат, который позволяет:
– задокументировать endpoints,
– описать схемы данных,
– зафиксировать ошибки,
– автоматически генерировать документацию и клиентов (если нужно).
Мы не будем вставлять огромный YAML на 30 страниц. Вместо этого зафиксируем структуру контракта, модели и правила, а также ключевые endpoints.
8.1. Основные принципы контракта
1) Стабильные модели ошибок (один формат на весь API).
2) Понятные коды ответа (не “всегда 200”).
3) Пагинация единым способом для всех списков.
4) Аутентификация единым способом.
5) Версионирование с первого дня.
И ещё правило, которое спасает нервы:
> Если вы не можете объяснить endpoint одной фразой – скорее всего, он делает слишком много.
8.2. Auth model (модель аутентификации)
Для простоты (и реальности) выбираем:
– Bearer token в заголовке Authorization: Bearer <token>
Где токен берётся:
– из POST /auth/login (и, возможно, POST /auth/register сразу возвращает токен)
В OpenAPI это описывается как security scheme типа HTTP bearer.
Минимальные endpoints auth
– POST /auth/register
– POST /auth/login
– POST /auth/logout (опционально; зависит от того, храним ли сессии на сервере)
– GET /me (получить профиль текущего пользователя)
8.3. Error model (единая модель ошибок)
Самая частая боль API – когда ошибки везде разные. Мы сделаем единый формат.
Предлагаемая модель:
json
{
"error": {
"code": "validation_error",
"message": "Invalid request",
"details": [
{ "field": "email", "message": "Invalid format" }
],
"request_id": "req_123"
}
}
Где:
– code – машинно-обрабатываемый код (snake_case)
– message – коротко для человека
– details – массив деталей (опционально)
– request_id – чтобы найти запрос в логах
Типовые коды ошибок
– validation_error → HTTP 400
– unauthorized → 401
– forbidden → 403
– not_found → 404
– conflict → 409
– rate_limited → 429
– internal_error → 500
Важно: для 500 мы не раскрываем внутренности. Логи – для нас, клиенту – “internal_error”.
8.4. Pagination model (модель пагинации)
Мы выбрали cursor pagination для основных списков.
Запрос
Параметры:
– limit (по умолчанию 20, максимум например 100)
– cursor (опционально)
Пример:
GET /tasks?limit=20&cursor=eyJjcmVhdGVkX2F0IjoiLi4uIiwiaWQiOiIuLi4ifQ==
Ответ
Единый формат списка:
json
{
"items": [ … ],
"page": {
"next_cursor": "....",
"has_more": true
}
}
Правила:
– если has_more=false, next_cursor может быть null
– курсор непрозрачный для клиента (он не обязан понимать содержимое)
Если для некоторых endpoint’ов нужен offset – лучше не смешивать. Но если уж смешали, делайте разные endpoint’ы или разные модели ответа, чтобы клиент не гадал.
8.5. Versioning (версионирование)
Варианты:
– через путь: /api/v1/…
– через заголовок: Accept: application/vnd.taskflow.v1+json
Для простоты и ясности берём версию в пути:
– /api/v1
Почему:
– проще дебажить,
– проще проксировать,
– проще объяснить.
Правило:
– ломающее изменение – новая версия (/v2)
– не ломающее – расширяем текущую версию (добавляем поля, новые endpoints)
8.6. Endpoints: фиксируем основной набор
Ниже – список endpoint’ов, который покрывает домен из главы 6. Формат: метод, путь, смысл, основные ответы.
8.6.1. Auth / User
POST /api/v1/auth/register
Создать пользователя.
Request:
– password
– name (опционально)
Responses:
– 201 → создан пользователь (+ возможно токен)
– 400 validation_error
– 409 conflict (email занят)
– 429 rate_limited
POST /api/v1/auth/login
Логин.
Request:
– password
Responses:
– 200 → токен
– 400 validation_error
– 401 unauthorized
– 429 rate_limited
GET /api/v1/me
Текущий пользователь.
Responses:
– 200 user profile
– 401 unauthorized
8.6.2. Workspaces и участники
POST /api/v1/workspaces
Создать workspace.
Headers:
– Idempotency-Key (рекомендуется)
Responses:
– 201 workspace
– 401 unauthorized
– 400 validation_error
GET /api/v1/workspaces
Список workspace, где пользователь состоит.
Responses:
– 200 list (можно без пагинации, если их мало, но лучше с limit/cursor)
GET /api/v1/workspaces/{workspaceId}
Получить workspace.
Responses:
– 200
– 403 forbidden (если нет доступа)
– 404 not_found (можно вернуть 404 вместо 403, чтобы не “палить” существование)
POST /api/v1/workspaces/{workspaceId}/members
Добавить участника (owner/admin).
Request:
– user_email или user_id
– role (admin/member)
Responses:
– 201
– 403
– 404
– 409 (уже участник)
PATCH /api/v1/workspaces/{workspaceId}/members/{userId}
Изменить роль.
Responses:
– 200
– 403
– 409 (например, нельзя понизить owner “в никуда”)
DELETE /api/v1/workspaces/{workspaceId}/members/{userId}
Удалить участника.
Responses:
– 204
– 403
8.6.3. Projects
POST /api/v1/workspaces/{workspaceId}/projects
Создать проект.
Headers:
– Idempotency-Key (рекомендуется)
Responses:
– 201
– 403
– 400
GET /api/v1/workspaces/{workspaceId}/projects
Список проектов (с фильтром status=active|archived).
Responses:
– 200 paginated list
GET /api/v1/projects/{projectId}
Получить проект.
Responses:
– 200
– 403/404
PATCH /api/v1/projects/{projectId}
Обновить проект (name/description/status).
Responses:
– 200
– 400
– 403
8.6.4. Tasks
POST /api/v1/projects/{projectId}/tasks
Создать задачу в проекте.
Headers:
– Idempotency-Key (рекомендуется)
Request:
– title (required)
– description (optional)
– assignee_user_id (optional)
– priority (optional)
– due_date (optional)
– labels (optional: массив label_id)
Responses:
– 201 task
– 400 validation_error
– 403 forbidden
– 409 conflict (если идемпотентность конфликтует)
GET /api/v1/tasks/{taskId}
Получить задачу.
Responses:
– 200
– 403/404
PATCH /api/v1/tasks/{taskId}
Обновить задачу (частично).
Responses:
– 200
– 400
– 403
DELETE /api/v1/tasks/{taskId}
Удалить (или архивировать) задачу.
Responses:
– 204
– 403
GET /api/v1/workspaces/{workspaceId}/tasks
Список задач в workspace с поиском/фильтрами.
Query params (примерный набор):
– q – поиск по тексту
– project_id
– status
– assignee_user_id
– label_id
– sort (например created_at, updated_at, due_date)
– order (asc|desc)
– limit, cursor
Responses:
– 200 paginated list
8.6.5. Comments
POST /api/v1/tasks/{taskId}/comments
Добавить комментарий.
Headers:
– Idempotency-Key (можно, но не обязательно; полезно)
Request:
– body
Responses:
– 201
– 400
– 403
GET /api/v1/tasks/{taskId}/comments
Список комментариев (pagination).
Responses:
– 200
DELETE /api/v1/comments/{commentId}
Удалить комментарий (если разрешено правилами).
Responses:
– 204
– 403
8.6.6. Labels
POST /api/v1/workspaces/{workspaceId}/labels
Создать метку.
Responses:
– 201
– 400
– 403
– 409 (если имя метки уникально в workspace)
GET /api/v1/workspaces/{workspaceId}/labels
Список меток.
Responses:
– 200 (можно без пагинации, но лучше с limit/cursor)
8.6.7. Webhooks / Notifications (минимальный контракт)
Если делаем webhooks:
– POST /api/v1/workspaces/{workspaceId}/webhooks – создать подписку
– GET /api/v1/workspaces/{workspaceId}/webhooks – список
– DELETE /api/v1/webhooks/{webhookId} – удалить
Модель webhook:
– url
– events (например task.created, comment.created)
– secret (для подписи)
– is_active
Для email-уведомлений в учебной версии часто достаточно “внутренней отправки” без внешнего API. Но события всё равно должны быть в аудит/логах.
8.7. Коды ответов и “мелкая гигиена API”
Несколько правил, которые повышают доверие к API:
– POST создание → 201 Created (+ тело созданного ресурса)
– DELETE успешный → 204 No Content
– PATCH успешный → 200 OK (+ обновлённый ресурс)
– GET список → 200 OK + { items, page }
И ещё:
– даты/время – в ISO 8601 (2026-01-01T12:34:56Z)
– идентификаторы – строки (часто удобнее и переносимее)
– не возвращайте поля, которые клиент не должен видеть (пароли, секреты)
8.8. Что можно установить, чтобы удобно работать с OpenAPI
– Swagger UI или любая UI-обёртка для просмотра спецификации
– Postman/Insomnia – импортировать спецификацию и тестировать запросы
– Редактор YAML/JSON с подсветкой схем (любой нормальный IDE/редактор справится)
8.9. Мини-итог главы
Мы превратили требования продукта в набор понятных договорённостей:
– endpoints,
– модель ошибок,
– модель пагинации,
– модель аутентификации,
– версия API.
Теперь можно переходить к реализации на любом языке, не споря “как лучше назвать поле” на каждом шаге – всё уже зафиксировано контрактом.