Практикум — проектирование контракта API
Практикум, шаг 2 из 8. Контракт пишем до кода на Python и C#. См. сценарий, теорию проектирования API.
Перед реализацией прогоните в песочнице сценарии «Нехватка остатка» и «Идемпотентность» — они соответствуют строкам таблиц ниже.
Принципы контракта
- Ресурсы — существительные во множественном числе:
/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:
{
"id": "ord_x9y8",
"status": "reserved",
"lines": [
{
"productId": "prod_a1b2",
"quantity": 3,
"unitPrice": 29.99,
"reservationId": "res_m3n4"
}
],
"total": 89.97,
"createdAt": "2026-05-27T10:05:00Z"
}
Фрагмент OpenAPI 3.1 (catalog)
Сохраните как catalog-api/openapi.yaml — источник правды для Postman и ревью:
openapi: 3.1.0
info:
title: OrderDesk Catalog API
version: 1.0.0
servers:
- url: http://localhost:8100
paths:
/api/v1/products:
get:
operationId: listProducts
parameters:
- name: page
in: query
schema: { type: integer, minimum: 1, default: 1 }
- name: pageSize
in: query
schema: { type: integer, minimum: 1, maximum: 100, default: 20 }
responses:
"200":
description: OK
post:
operationId: createProduct
security: [{ bearerAuth: [] }]
responses:
"201":
description: Created
/api/v1/reservations:
post:
operationId: createReservation
security: [{ apiKeyAuth: [] }]
parameters:
- name: Idempotency-Key
in: header
required: true
schema: { type: string, maxLength: 64 }
responses:
"201":
description: Reserved
"409":
description: Insufficient stock
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
apiKeyAuth:
type: apiKey
in: header
name: X-Api-Key
Фрагмент OpenAPI (orders-api)
paths:
/api/v1/auth/token:
post:
operationId: issueToken
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [username, password]
properties:
username: { type: string }
password: { type: string, format: password }
responses:
"200":
description: JWT выдан
/api/v1/orders:
post:
operationId: createOrder
security: [{ bearerAuth: [] }]
responses:
"201": { description: Created }
"409": { description: Stock conflict }
"502": { description: Catalog unreachable }
/api/v1/orders/{orderId}/confirm:
post:
operationId: confirmOrder
security: [{ bearerAuth: [] }]
Полные спецификации храните рядом с кодом (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 — Модели данных и маппинг.
См. также
Другие статьи этого же раздела в боковом меню (как на странице "О разделе"). Два сервиса OrderDesk: каталог на Python и заказы на C#, границы ответственности, потоки REST и WebSocket. Доменные сущности OrderDesk, DTO для REST, маппинг Python (Pydantic) и C# (record + ручной маппер). FastAPI, SQLite, эндпоинты товаров и резервирования, Pydantic и проверка через uvicorn. ASP.NET Core 8, Minimal API, HttpClient к catalog-api, SQLite и создание заказа с резервом. JWT, API-ключ между сервисами, HTTPS, таймауты, идемпотентность и заголовок X-Request-Id в OrderDesk. Протокол JSON-сообщений, hub в ASP.NET Core, heartbeat и подписка клиента на статусы OrderDesk. Коллекция Postman, переменные окружения и сквозной сценарий OrderDesk — товар, заказ, WebSocket.Практикум — сценарий и архитектура OrderDesk
Практикум — модели данных и маппинг DTO
Практикум — сервис каталога на Python
Практикум — сервис заказов на C#
Практикум — безопасность и устойчивость
Практикум — WebSocket и события заказов
Практикум — проверка в Postman