Перейти к содержанию

Практикум: API заказов на четырёх библиотеках

API заказов читает PostgreSQL и запрашивает остаток у локального склада. Соединяем servicewright, sqlalchemy-foundation-kit, clientwright и deadline-budget. Пример к статье «Зачем выделять инфраструктуру Python-сервисов в библиотеки».

Запуск

Нужны работающий Docker и uv. Запустите из корня репозитория сайта:

cd docs/blog/lab/2026-09-07-what-every-microservice-reimplements
uv run --no-project --python 3.13 --with-requirements requirements.txt python run_skeleton.py

Скрипт создаёт временный контейнер postgres:17-alpine и два локальных HTTP-сервера с автоматически выбранными портами. При выходе Testcontainers удаляет свою БД. Первая загрузка образа и запуск контейнера могут занять больше времени, чем проверки приложения. Для отсутствующих в кеше образов, Python и зависимостей нужен доступ к сети.

В requirements.txt зафиксированы servicewright 0.13.1, sqlalchemy-foundation-kit 0.4.0, clientwright 0.5.0, deadline-budget 0.1.3 и основные зависимости интеграционного примера. Используется Python 3.13. Тестовую схему создаёт скрипт; в приложении вместо этого нужны миграции.

Что проверяем

  • Существующий заказ читается по ID, остаток запрашивается по SKU. Разное количество товара в заказе даёт разные значения can_fulfill.
  • Неизвестный заказ возвращает 404 без обращения к складу. Нулевой, отрицательный, слишком большой и нечисловой бюджет отклоняются с 422.
  • После одного 503 склада выполняется один успешный повтор с меньшим переданным бюджетом времени. Постоянный 503 возвращается клиенту после двух попыток.
  • Некорректный ответ склада превращается в 503, а зависший запрос в 504 по дедлайну операции.
  • В этом примере readiness зависит от PostgreSQL и остаётся успешным при недоступном складе.
  • Соединение с БД возвращается в пул до HTTP-запроса. При остановке readiness меняется, активный запрос получает время закончить работу, затем закрываются общий клиент и менеджер БД.
  • Ошибка проверки PostgreSQL при запуске не даёт запустить HTTP-сервер; уже открытые ресурсы закрываются.
  • Прикладной сценарий работает с обычными объектами, а HTTP-адаптер проверяется без запуска servicewright и FastAPI.

При несовпадении утверждений скрипт завершается с ошибкой. Ожидания ограничены таймаутами; точные миллисекунды не зашиты в проверки. Сценарий остановки использует событие runtime, а не сигнал ОС, и отпускает задержанный запрос в пределах времени на завершение.

Файлы

  • orders.py: данные заказа, два небольших протокола и прикладное правило.
  • adapters.py: SQL-запрос, настройки общего HTTP-клиента и проверка ответа склада.
  • service.py: бюджет запроса, HTTP-ошибки, области ресурсов и настройки runtime.
  • lab_support.py: управляемый HTTP-склад и средства запуска сервиса для проверок.
  • run_skeleton.py: временная БД, тестовые данные и все проверки.
  • requirements.txt: зафиксированные зависимости.

При отдельном запуске service.py читает DATABASE_URL, WAREHOUSE_URL и необязательный PORT (по умолчанию 8080), затем вызывает run_sync(). Нужны БД с применёнными миграциями и склад, который отвечает {"available": 3} на /stock/{sku}. Автоматический практикум предоставляет их локально.

Просмотр не резервирует товар и не создаёт распределённую транзакцию. Аутентификация, TLS, настройки развёртывания и нагрузочные проверки остаются за пределами этого локального примера.