6.2 KiB
6.2 KiB
spec/overview.md
Контекст, специфичный для проекта агрегатора служб доставки. Глобальные правила определены в AGENTS.md.
Проект
Название: Агрегатор служб доставки (бэкенд) Язык: Python 3.14 Назначение: Агрегировать расчёты стоимости доставки от нескольких служб и отдавать единый ответ фронтенду через унифицированный API.
Продуктовые требования
- Принимать запрос на расчёт стоимости доставки (откуда, куда, вес, габариты)
- Опрашивать всех зарегистрированных провайдеров параллельно
- Возвращать унифицированный список тарифов, отсортированных по цене
- Если провайдер вернул ошибку или не ответил вовремя — исключить его из результата, не падая целиком
- Кешировать ответы провайдеров для исключения повторных внешних запросов. Время кэширования вынести в конфиг
- Применять к цене, полученной от провайдера, конфигурируемый мультипликатор и округлять итоговую цену до целого значения
Провайдеры
Фаза 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]- Парсинг и валидация входных данных через Pydantic
- Маппинг исключений сервиса в HTTP-ответы
- Вызывает ровно один метод Service:
AggregatorService.get_all_prices()
Service (app/services/aggregator.py)
AggregatorService.get_all_prices(request: DeliveryRequest) -> list[DeliveryPrice]- Распределяет запросы по всем зарегистрированным провайдерам через
asyncio.gather(..., return_exceptions=True) - Фильтрует упавшие результаты
- Сортирует тарифы по цене
- Не содержит бизнес-логики и логики, специфичной для провайдеров
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: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- Каждый адаптер владеет своей конфигурацией; наружу экспонирует только
get_price()
Модели данных
Входная: 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
Технологический стек
| Задача | Инструмент |
|---|---|
| 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
├── repositories/
│ └── cache/
│ └── redis_cache.py # Repository
├── schemas/
│ ├── request.py
│ └── response.py
└── config.py
Инфраструктура
# docker-compose сервисы
app # FastAPI-приложение
redis # Кеш тарифов