The before code updates the order and publishes an event separately, risking lost events if a crash occurs between steps. The after code writes the order update and event to the outbox table in one transaction, ensuring atomicity. A separate processor reliably publishes events from the outbox.
### Before (without outbox pattern):
# Update DB and publish event separately
def update_order(order_id, new_status):
db.execute("UPDATE orders SET status = %s WHERE id = %s", (new_status, order_id))
# If crash happens here, event is lost
event_bus.publish({"order_id": order_id, "status": new_status})
### After (with outbox pattern):
# Write update and event in one DB transaction
def update_order(order_id, new_status):
with db.transaction() as txn:
txn.execute("UPDATE orders SET status = %s WHERE id = %s", (new_status, order_id))
txn.execute("INSERT INTO outbox (event_type, payload) VALUES (%s, %s)",
("OrderStatusChanged", json.dumps({"order_id": order_id, "status": new_status})))
# Separate process reads outbox and publishes events
def outbox_processor():
events = db.query("SELECT id, event_type, payload FROM outbox WHERE published = FALSE")
for event in events:
event_bus.publish(json.loads(event.payload))
db.execute("UPDATE outbox SET published = TRUE WHERE id = %s", (event.id,))