Практикум: повторная доставка без второго счёта¶
Запускаемый код из статьи.
Нужен работающий Docker. Скрипт создаёт и удаляет собственные контейнеры PostgreSQL
и Kafka. Выполните команду из docs/blog/lab/2026-09-07-exactly-once-effects
в клоне репозитория:
В requirements.txt закреплены omni-box 0.3.0, aiokafka 0.14.0,
SQLAlchemy 2.0.54, asyncpg 0.31.0 и testcontainers 4.15.0.
Образы: postgres:17-alpine и confluentinc/cp-kafka:7.6.0 в режиме KRaft.
Для PostgreSQL явно указан драйвер asyncpg.
В models.py определены заказы, счета и две ORM-модели omni-box.
event_flow.py содержит код из статьи и настройку producer для relay.
lab_support.py запускает контейнеры и читает Kafka с ограничением времени;
его используют все три практикума. Для простоты два сервиса здесь работают с одной БД.
Проверяются следующие результаты:
- commit заказа и сбой до отправки оставляют заказ без события;
- отправка события и откат транзакции оставляют событие без заказа;
- заказ и его событие Outbox откатываются вместе;
- сбой после подтверждения Kafka, но до commit Outbox приводит к повторной публикации;
- у двух записей Kafka разные offset и один
event_id; - Inbox обрабатывает первую запись, пропускает вторую и оставляет один счёт.
Идентификатор заказа в счёте намеренно не уникален: ограничение не должно скрывать повторный вызов обработчика. В рабочей схеме правило об одном счёте на заказ стоит дополнительно закрепить собственным ограничением.
Сбои на границах транзакций воспроизводятся исключениями, а не завершением процесса.
Стенд с одним брокером проверяет доставку и изменения в БД; репликация и переключение
брокеров сюда не входят. Проверка Kafka читает до заранее полученного конечного offset,
а не полагается на один опрос с таймаутом.
Каждая строка PASS появляется после проверок; при нарушении условия скрипт завершается с ошибкой.
Продолжение: сбой обработчика и границы Inbox или недоступность брокера и восстановление.