Практикум: 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, настройки развёртывания и нагрузочные проверки остаются за пределами этого локального примера.