Publishing an OrderPaid event reliably with the transactional outbox pattern

Mock CTO Interview November 13, 2026 python

You: "Transactional outbox.

python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# ======================================================================
# Q: CTO: "How do you publish the OrderPaid event so it is not lost if the
# broker goes down?"
#
# You: "Transactional outbox. The order change and the outbox row are written in
# the same transaction. A relay publishes the rows that have not been sent. With
# skip_locked, multiple relays can run without picking up the same events.
# Delivery is at-least-once, so consumers deduplicate by event_id."
# ======================================================================

with transaction.atomic():
    order.mark_paid()
    OutboxEvent.objects.create(event_type='order.paid', aggregate_id=order.id,
                               payload={'order_id': order.id})

@shared_task
def relay_outbox():
    with transaction.atomic():
        batch = (OutboxEvent.objects.select_for_update(skip_locked=True)
                 .filter(published_at=None).order_by('id')[:100])
        for ev in batch:
            broker.publish(ev.event_type, ev.payload, message_id=str(ev.id))
            ev.published_at = timezone.now()
            ev.save(update_fields=['published_at'])