Практикум — проектирование контракта API
Практикум, шаг 2 из 8. Контракт пишем до кода на Python и C#. См. сценарий, теорию проектирования API.
Play ITЗагрузка интерактивного демо…
Перед реализацией прогоните в песочнице сценарии "Нехватка остатка" и "Идемпотентность" — они соответствуют строкам таблиц ниже.
Принципы контракта
- Ресурсы — существительные во множественном числе —
/products,/orders,/reservations. - Действия вне CRUD — подресурс или отдельный ресурс:
POST /reservations,POST /orders/{id}/confirm. - Тело запроса и ответа — JSON, даты в ISO 8601 UTC (
2026-05-27T10:15:00Z). - Ошибки — единый формат Problem Details (RFC 7807).
{
"type": "https://orderdesk.local/errors/insufficient-stock",
"title": "Insufficient stock",
"status": 409,
"detail": "Product prod_7 has 2 units, requested 5",
"instance": "/api/v1/reservations"
}
catalog-api (Python) — публичный и внутренний API
Базовый URL: http://localhost:8100
| Метод | Путь | Назначение | Успех | Идемпотентность |
|---|---|---|---|---|
GET | /api/v1/products | Список товаров (пагинация ?page=1&pageSize=20) | 200 | да |
POST | /api/v1/products | Создать товар | 201 | нет |
GET | /api/v1/products/{productId} | Карточка товара | 200 / 404 | да |
PATCH | /api/v1/products/{productId} | Частичное обновление (имя, цена) | 200 | да |
POST | /api/v1/reservations | Зарезервировать остаток | 201 / 409 | нет* |
DELETE | /api/v1/reservations/{reservationId} | Отменить резерв | 204 | да |
* Повтор POST /reservations с тем же заголовком Idempotency-Key возвращает тот же 201 и тело — см. шаг 6.
Заголовки (catalog):
| Заголовок | Кто шлёт | Зачем |
|---|---|---|
Authorization: Bearer <jwt> | Клиент (Postman) | Доступ к CRUD товаров |
X-Api-Key | orders-api | Межсервисные POST/DELETE резерва |
Idempotency-Key | orders-api | Безопасный повтор резерва |
X-Request-Id | любой | Трассировка в логах |
orders-api (C#) — API для клиентов
Базовый URL: http://localhost:5200
| Метод | Путь | Назначение | Успех |
|---|---|---|---|
POST | /api/v1/auth/token | Выдать JWT (учебный login/password) | 200 |
GET | /api/v1/orders | Список заказов текущего пользователя | 200 |
POST | /api/v1/orders | Создать заказ и зарезервировать в каталоге | 201 / 502 |
GET | /api/v1/orders/{orderId} | Детали заказа | 200 / 404 |
POST | /api/v1/orders/{orderId}/confirm | Подтвердить после оплаты | 200 |
POST | /api/v1/orders/{orderId}/cancel | Отменить, снять резерв в каталоге | 200 |
WebSocket: ws://localhost:5200/ws/orders?access_token=<jwt>
Тела запросов и ответов (ключевые)
Создание товара
POST /api/v1/products
{
"sku": "WB-42",
"name": "Wireless mouse",
"price": 29.99,
"stockAvailable": 100
}
Ответ 201:
{
"id": "prod_a1b2",
"sku": "WB-42",
"name": "Wireless mouse",
"price": 29.99,
"stockAvailable": 100,
"createdAt": "2026-05-27T10:00:00Z"
}
Резервирование (вызов из orders-api)
POST /api/v1/reservations
{
"productId": "prod_a1b2",
"quantity": 3,
"orderRef": "ord_x9y8"
}
Ответ 201:
{
"reservationId": "res_m3n4",
"productId": "prod_a1b2",
"quantity": 3,
"expiresAt": "2026-05-27T11:00:00Z"
}
Создание заказа
POST /api/v1/orders
{
"lines": [
{ "productId": "prod_a1b2", "quantity": 3 }
]
}
Ответ 201:
Код ITЗагрузка примера кода…
Фрагмент OpenAPI 3.1 (catalog)
Сохраните как catalog-api/openapi.yaml — источник правды для Postman и ревью:
Код ITЗагрузка примера кода…
Фрагмент OpenAPI (orders-api)
Код ITЗагрузка примера кода…
Полные спецификации храните рядом с кодом (catalog-api/openapi.yaml, orders-api/openapi.yaml). В Postman импортируйте оба файла через Import → OpenAPI — коллекция и примеры тел подтянутся автоматически.
Пагинация и метаданные списков
GET /api/v1/products и GET /api/v1/orders возвращают обёртку (рекомендуется для роста данных):
{
"items": [ { "id": "prod_a1b2", "name": "…" } ],
"page": 1,
"pageSize": 20,
"total": 134
}
Заголовок Link с rel="next" — альтернатива query-параметрам; в практикуме достаточно ?page= и ?pageSize=.
Матрица ответственности по кодам HTTP
| Код | Когда |
|---|---|
400 | Невалидное тело (Pydantic / FluentValidation) |
401 | Нет или просрочен JWT |
403 | JWT есть, прав недостаточно |
404 | Ресурс не найден |
409 | Конфликт остатка при резерве |
422 | Семантическая ошибка (пустой заказ) |
502 | orders-api не достучался до catalog-api |
503 | Сервис временно недоступен (circuit breaker) |
Следующий шаг
Контракт задаёт форму JSON. Внутри каждого сервиса нужны свои сущности и DTO — Модели данных и маппинг.