198 lines
8.0 KiB
Markdown
198 lines
8.0 KiB
Markdown
# spec/overview.md
|
|
|
|
Контекст, специфичный для проекта агрегатора служб доставки.
|
|
Глобальные правила определены в AGENTS.md.
|
|
|
|
---
|
|
|
|
## Проект
|
|
|
|
**Название:** Агрегатор служб доставки (бэкенд)
|
|
**Язык:** Python 3.14
|
|
**Назначение:** Агрегировать расчёты стоимости доставки от нескольких служб и отдавать единый ответ фронтенду через унифицированный API.
|
|
|
|
---
|
|
|
|
## Продуктовые требования
|
|
|
|
- Принимать запрос на расчёт стоимости доставки (откуда, куда, вес, габариты)
|
|
- Принимать запрос на создание заказа CDEK по контракту из `http-client.http` для сценария "доставка, до двери"
|
|
- Опрашивать всех зарегистрированных провайдеров параллельно
|
|
- Возвращать унифицированный список тарифов, отсортированных по цене
|
|
- Если провайдер вернул ошибку или не ответил вовремя — исключить его из результата, не падая целиком
|
|
- Кешировать ответы провайдеров для исключения повторных внешних запросов. Время кэширования вынести в конфиг
|
|
- Применять к цене, полученной от провайдера, конфигурируемый мультипликатор и округлять итоговую цену до целого значения
|
|
|
|
---
|
|
|
|
## Провайдеры
|
|
|
|
### Фаза 1
|
|
- **CDEK** — https://apidoc.cdek.ru/
|
|
- Аутентификация: OAuth2 (client credentials)
|
|
- Операции: расчёт тарифа, регистрация заказа
|
|
- Cache TTL: 15 минут
|
|
|
|
### Фаза 2+
|
|
- Дополнительные провайдеры (Boxberry, DHL и др.) — подключаются через интерфейс `DeliveryProvider` без изменений в логике агрегации
|
|
|
|
---
|
|
|
|
## Компоненты
|
|
|
|
### Controller (`app/controllers/v1/delivery.py`)
|
|
- `POST /api/v1/delivery/price` — принимает `DeliveryRequest`, возвращает `list[DeliveryPrice]`
|
|
- `POST /api/v1/delivery/order` — принимает `OrderCreateRequest`, возвращает `OrderCreateResponse`
|
|
- Парсинг и валидация входных данных через Pydantic
|
|
- Маппинг исключений сервиса в HTTP-ответы
|
|
- Каждый endpoint вызывает ровно один метод Service: `AggregatorService.get_all_prices()` или `AggregatorService.create_order()`
|
|
|
|
### Service (`app/services/aggregator.py`)
|
|
- `AggregatorService.get_all_prices(request: DeliveryRequest) -> list[DeliveryPrice]`
|
|
- Распределяет запросы по всем зарегистрированным провайдерам через `asyncio.gather(..., return_exceptions=True)`
|
|
- Фильтрует упавшие результаты
|
|
- Сортирует тарифы по цене
|
|
- `AggregatorService.create_order(request: OrderCreateRequest) -> OrderCreateResponse`
|
|
- `AggregatorService.create_order()` оркестрирует регистрацию заказа в CDEK через injected adapter dependency
|
|
- Service не содержит бизнес-логики и provider HTTP-деталей
|
|
|
|
### Business Logic (`app/domain/`)
|
|
- Правила сортировки и фильтрации тарифов
|
|
- Логика сравнения цен
|
|
- Правила применения конфигурируемого мультипликатора к ценам провайдеров
|
|
- Округление цены после применения мультипликатора до целого значения по детерминированному правилу
|
|
- Нормализация входных данных (например, правила округления веса)
|
|
- Чистые функции, без IO, без зависимостей от фреймворков
|
|
|
|
### Repository (`app/repositories/cache/`)
|
|
- `PriceCache` — хранилище ключ/значение на базе Redis
|
|
- Операции: `get(key)`, `set(key, value, ttl)`, `invalidate(key)`
|
|
- Без бизнес-решений; формирование ключа — ответственность Adapter
|
|
|
|
### Adapter (`app/adapters/delivery_providers`)
|
|
- `base.py` — абстрактный интерфейс `DeliveryProvider`:
|
|
```python
|
|
class DeliveryProvider(ABC):
|
|
name: str
|
|
async def get_price(self, request: DeliveryRequest) -> DeliveryPrice: ...
|
|
```
|
|
- `cdek/client.py` — HTTP-клиент (httpx AsyncClient), аутентификация, ретраи, таймаут (10с)
|
|
- `cdek/auth.py` — управление OAuth2-токеном
|
|
- `cdek/mapper.py` — ответ CDEK → `DeliveryPrice`
|
|
- `cdek/order_mapper.py` — request/response mapping для регистрации заказа CDEK
|
|
- Каждый адаптер владеет своей конфигурацией; наружу экспонирует только service-facing methods, необходимые соответствующему use-case
|
|
|
|
Для сценария создания заказа CDEK adapter принимает валидированную order model, отправляет контракт `Регистрация заказа (тип "доставка", до двери)` из `http-client.http` и возвращает внутреннюю response model без утечки HTTP-деталей в Service.
|
|
|
|
---
|
|
|
|
## Модели данных
|
|
|
|
### Входная: `DeliveryRequest`
|
|
```
|
|
entity: Enum(individual, legal)
|
|
from_city: str
|
|
to_city: str
|
|
weight_kg: float
|
|
length_cm: float
|
|
width_cm: float
|
|
height_cm: float
|
|
```
|
|
|
|
### Выходная: `DeliveryPrice`
|
|
```
|
|
provider: str
|
|
service_name: str
|
|
price: Decimal
|
|
currency: str
|
|
delivery_days_min: int
|
|
delivery_days_max: int
|
|
```
|
|
|
|
### Входная: `OrderCreateRequest`
|
|
```
|
|
type: Literal[2]
|
|
tariff_code: Literal[535]
|
|
comment: str | None
|
|
sender:
|
|
company: str | None
|
|
name: str
|
|
email: str
|
|
phones: list[{number: str}]
|
|
recipient:
|
|
name: str
|
|
email: str
|
|
phones: list[{number: str}]
|
|
from_location:
|
|
address: str
|
|
city: str
|
|
country_code: str
|
|
to_location:
|
|
address: str
|
|
city: str
|
|
country_code: str
|
|
services: list[{code: str, parameter: str}]
|
|
packages: list[{number: str, weight: int, length: int, width: int, height: int, comment: str | None}]
|
|
```
|
|
|
|
### Выходная: `OrderCreateResponse`
|
|
```
|
|
provider: str
|
|
order_uuid: str
|
|
```
|
|
|
|
---
|
|
|
|
## Технологический стек
|
|
|
|
| Задача | Инструмент |
|
|
|---|---|
|
|
| Web-фреймворк | FastAPI |
|
|
| HTTP-клиент | httpx (async) |
|
|
| Валидация | Pydantic v2 |
|
|
| Кеш | Redis |
|
|
| Конфигурация | pydantic-settings |
|
|
| Сервер | Uvicorn |
|
|
|
|
---
|
|
|
|
## Структура проекта
|
|
|
|
```
|
|
app/
|
|
├── controllers/
|
|
│ ├── http_client.py
|
|
│ └── v1/
|
|
│ └── delivery.py # Controller
|
|
├── services/
|
|
│ └── aggregator.py # Service
|
|
├── domain/
|
|
│ └── price.py # Business Logic
|
|
├── adapters/
|
|
│ └── delivery_providers/
|
|
│ ├── base.py # Интерфейс Adapter
|
|
│ └── cdek/
|
|
│ ├── client.py
|
|
│ ├── auth.py
|
|
│ ├── mapper.py
|
|
│ └── order_mapper.py
|
|
├── repositories/
|
|
│ └── cache/
|
|
│ └── redis_cache.py # Repository
|
|
├── schemas/
|
|
│ ├── request.py
|
|
│ ├── response.py
|
|
│ └── order.py
|
|
└── config.py
|
|
```
|
|
|
|
---
|
|
|
|
## Инфраструктура
|
|
|
|
```yaml
|
|
# docker-compose сервисы
|
|
app # FastAPI-приложение
|
|
redis # Кеш тарифов
|
|
```
|