Стек:
FastAPI
PostgreSQL
Docker
Pytest
Redis
Celery
RabbitMQ
Асинхронное приложение по работе с меню ресторана. Реализованы 3 сущности: Меню, Подменю, Блюдо. Зависимости:
- У меню есть подменю
- В подменю есть блюда
Реализовано кэширование, в качестве кэш-хранилища используется Redis.
Выгрузка меню в excel — надёжное фоновое задание (Celery + RabbitMQ + transactional outbox в PostgreSQL), см. раздел «Фоновые задания».
cp .env.example .env # и задать свои пароли
docker-compose up -d
Процессы: web (API), dispatcher (outbox + сверка), celery (worker), db, redis, rabbitmq.
docker-compose -f docker-compose.tests.yml up --build --abort-on-container-exit --exit-code-from test_app
Тесты идут на реальном PostgreSQL (test_db), схема создаётся из моделей.
Документация доступна по адресу http://127.0.0.1:8000/docs
- POST
/api/v1/dumpнаполнение базы готовыми данными
- POST
/api/v1/reportзапрос для генерации отчета в формате xlsx - GET
/api/v1/report/{task_id}запрос статуса задачи (200, когда отчёт готов, иначе425) - GET
/api/v1/report/{task_id}/downloadскачивание отчета
- POST
/api/v1/jobsтело{"kind":"menu_report"};202, заголовокLocation - GET
/api/v1/jobs/{id}состояние,attempts,max_attempts, код и безопасное описание ошибки - GET
/api/v1/jobs?status=failed&limit=20&offset=0список метаданных (limit≤ 100) - GET
/api/v1/jobs/{id}/resultXLSXmenu-report-{id}.xlsx;404— нет задания,409— результата ещё нет
- POST
/api/v1/menusсоздание меню - GET
/api/v1/menusпросмотр списка меню - PATCH
/api/v1/menus/{menu_id}обновление меню - GET
/api/v1/menus/{menu_id}просмотр определенного меню - DELETE
/api/v1/menus/{menu_id}удаление меню
- POST
/api/v1/menus/{menu_id}/submenus/создание подменю - GET
/api/v1/menus/{menu_id}/submenus/просмотр списка подменю - PATCH
/api/v1/menus/{menu_id}/submenus/{submenu_id}обновление подменю - GET
/api/v1/menus/{menu_id}/submenus/{submenu_id}просмотр определенного подменю - DELETE
/api/v1/menus/{menu_id}/submenus/{submenu_id}удаление подменю
- POST
/api/v1/menus/{menu_id}/submenus/{submenu_id}/dishesсоздание блюда - GET
/api/v1/menus/{menu_id}/submenus/{submenu_id}/dishesпросмотр списка блюд - PATCH
/api/v1/menus/{menu_id}/submenus/{submenu_id}/dishes/{dish_id}обновление блюда - GET
/api/v1/menus/{menu_id}/submenus/{submenu_id}/dishes/{dish_id}просмотр определенного блюда - DELETE
/api/v1/menus/{menu_id}/submenus/{submenu_id}/dishes/{dish_id}удаление блюда
Дизайн: docs/superpowers/specs/2026-10-03-reliable-jobs-design.md.
Состояния: queued → running → succeeded; при ошибке retry_wait (до 3 попыток, задержки 5 и 30 с) и failed.
- Приём: снимок меню, запись в
jobsи событие вoutboxфиксируются одной транзакцией;202не зависит от RabbitMQ. dispatcherпубликует события с publisher confirms иmandatory; строка отмечается отправленной только после подтверждения брокера. Каждые 10 с он же восстанавливает задания с истёкшим владением и неподхваченные события.- Worker получает только
job_id, номер попытки и ID события; состояние и результат (XLSX, SHA-256) хранятся в PostgreSQL. Результат и переход вsucceeded— одна транзакция под токеном владения, поэтому повторная доставка не создаёт второй результат. - Терминальные ошибки пишутся в очередь
jobs.failed(событие с постоянным ID), список ошибочных заданий —GET /api/v1/jobs?status=failed. - Интервалы настраиваются переменными
JOB_LEASE_SECONDS,JOB_RETRY_DELAYS,JOB_TIME_LIMIT,RECONCILE_INTERVAL,REDELIVERY_TIMEOUT,PUBLISH_TIMEOUTи др. (см.app/core/config.py).
Испытания идут на реальных PostgreSQL, RabbitMQ, диспетчере и worker (нужны Docker Compose и Python 3.10+):
e2e/run.sh
Стенд docker-compose.e2e.yml сокращает интервалы, а worker в нём поддерживает тестовые барьеры (JOB_TEST_BARRIER=before_commit|after_commit) и принудительную ошибку (JOB_TEST_FAIL=1); в обычном запуске они выключены.
| Сценарий | Что проверяется |
|---|---|
RabbitMQ остановлен во время POST /jobs |
202, строки в jobs и outbox; после запуска брокера — succeeded |
| worker убит во время построения XLSX | после истечения владения задание выполняется заново, результат один |
| worker убит после коммита результата, до ack | повторная доставка не меняет succeeded, COUNT(job_results)=1, SHA-256 тот же |
| дубль публикации и два worker | одно задание, один результат |
| постоянная ошибка | ровно 3 попытки, failed, событие в jobs.failed |
| сбой транзакции создания | нет 202 и нет задания без события outbox (tests/test_jobs_api.py) |
Вручную: docker-compose stop rabbitmq, curl -X POST localhost:8000/api/v1/jobs -H 'content-type: application/json' -d '{"kind":"menu_report"}', затем docker-compose start rabbitmq и GET /api/v1/jobs/{id}.
Миграции: alembic upgrade head (проверено up/down/up на PostgreSQL).