Lab: exactly-once effects¶
Runnable code from the article. Docker must be running. The script creates and removes its own PostgreSQL and Kafka containers. Run from this directory in a repository checkout:
Dependencies are pinned in requirements.txt: omni-box 0.3.0, aiokafka 0.14.0,
SQLAlchemy 2.0.54, asyncpg 0.31.0 and testcontainers 4.15.0.
Images: postgres:17-alpine and confluentinc/cp-kafka:7.6.0 in KRaft mode.
The PostgreSQL driver is explicitly asyncpg.
models.py defines orders, invoices and the two omni-box ORM models.
event_flow.py contains the code printed in the article and the relay's producer setup.
lab_support.py provides the containers and bounded Kafka reads used by all three labs.
The two services share one database here to keep the fixture small.
Assertions verify:
- order commit followed by a simulated crash leaves an order without an event;
- publication followed by transaction rollback leaves an event without an order;
- an order and its Outbox event roll back together;
- failure after Kafka acknowledgement but before the Outbox commit causes another publication;
- the two Kafka records have different offsets and the same
event_id; - Inbox processes the first record, skips the second and leaves one invoice.
The invoice's order ID is deliberately not unique: a constraint must not hide a duplicate handler execution. A production rule limiting invoices per order should also have its own constraint.
Failures at transaction boundaries are injected exceptions, not killed processes.
This single-broker setup tests delivery and database effects, not replication or cluster
failover. Kafka audits read to a captured end offset rather than trusting one timed poll.
Every PASS line follows assertions; a failed assertion exits the script with an error.
Continue with handler failure and Inbox boundaries or a paused broker and recovery.