How to Implement an Outbox Table Poll Publisher in Python
This code simulates an outbox pattern with a class that polls for pending records and publishes them as JSON messages, removing only those that are due.
Python code
52 linesimport time
import json
from dataclasses import dataclass, asdict
from datetime import datetime, timedelta
@dataclass
class OutboxRecord:
id: int
topic: str
payload: dict
created_at: datetime
class OutboxPollPublisher:
def __init__(self, poll_interval_seconds=1):
self.poll_interval = poll_interval_seconds
self.outbox = []
self.next_id = 1
def add_outbox_records(self, records):
"""Simulate new rows appearing in the outbox table."""
self.outbox.extend(records)
def publish_pending(self):
"""Poll for pending records and 'publish' them (simulate)."""
published = []
for record in self.outbox:
if record.created_at <= datetime.now():
message = {
"event_id": record.id,
"topic": record.topic,
"payload": record.payload,
"published_at": datetime.now().isoformat()
}
print(f"PUBLISHED: {json.dumps(message)}")
published.append(record)
# Remove published records
self.outbox = [r for r in self.outbox if r not in published]
return len(published)
if __name__ == "__main__":
publisher = OutboxPollPublisher()
# Simulate records being inserted into the outbox table
publisher.add_outbox_records([
OutboxRecord(id=1, topic="order.created", payload={"order_id": "A1"}, created_at=datetime.now() - timedelta(seconds=2)),
OutboxRecord(id=2, topic="payment.processed", payload={"payment_id": "P99"}, created_at=datetime.now() - timedelta(seconds=1)),
OutboxRecord(id=3, topic="user.registered", payload={"user_id": 42}, created_at=datetime.now() + timedelta(seconds=5)) # Future — won't publish yet
])
print(f"Polling outbox. Records pending: {len(publisher.outbox)}")
publisher.publish_pending()
print(f"After poll, remaining: {len(publisher.outbox)}")
Output
Polling outbox. Records pending: 3
PUBLISHED: {"event_id": 1, "topic": "order.created", "payload": {"order_id": "A1"}, "published_at": "2025-04-12T10:00:00.123456"}
PUBLISHED: {"event_id": 2, "topic": "payment.processed", "payload": {"payment_id": "P99"}, "published_at": "2025-04-12T10:00:00.123456"}
After poll, remaining: 1
How it works
The OutboxPollPublisher maintains an in‑memory list of OutboxRecord objects. When publish_pending() is called, it filters records whose created_at is in the past, simulates publishing by printing a JSON message, and then rebuilds the outbox list to exclude those published records. The comparison uses datetime.now() at each poll, so only records that have reached their scheduled time are processed. This mirrors the database outbox pattern where a worker polls the outbox table and publishes events to a message broker.
Common mistakes
- Comparing datetime objects without timezone awareness — naive datetime vs aware can cause off‑by‑hour errors.
- Mutating the list while iterating over it — here we rebuild the list after, which is safe.
- Not handling idempotency — if the same record is polled twice, duplicate events may be published.
Variations
- Use a database query with a SQL `SELECT ... WHERE created_at <= NOW()` and mark records as processed in a transaction.
- Replace the `print` with an actual message broker publisher call (e.g., Kafka, RabbitMQ).
Real-world use cases
- Publishing domain events to a message broker after a database transaction commits, ensuring reliability.
- Implementing a worker that polls an outbox table to sync data changes to a search index or cache.
- Building a scheduler that releases deferred jobs by checking a timestamp before sending notifications.
Sponsored
More from Streaming & messaging
- At Most Once Fire-and-Forget Mock in Python easy
- Batch Consume Process Commit Pattern in Python medium
- Build a Streaming Messaging Helper in Python easy
- Dead Letter Queue Failed Messages List Mock in Python easy
- Dedupe processed message IDs in Python easy
- Event Envelope with Schema Version Field in Python easy
Keep learning
Related tutorials and quizzes for this topic.