Introduction
'Saved to DB but event never fired' — Transactional Outbox combines business logic write and event delivery in the same transaction for guaranteed delivery. Let Claude Code design this.
CLAUDE.md Rules
## Outbox Pattern Rules
- Outbox write in same transaction as business logic
- At-least-once delivery (idempotency on consumer)
- FOR UPDATE SKIP LOCKED for safe batch processing
- Debezium CDC: WAL-based real-time delivery
Generated Implementation
// Business logic + outbox in same transaction
export async function createOrderWithEvent(data: CreateOrderInput) {
return prisma.$transaction(async (tx) => {
const order = await tx.order.create({ data: { userId: data.userId } });
await tx.outboxEvent.create({
data: {
aggregateType: 'Order',
aggregateId: order.id,
eventType: 'OrderCreated',
payload: { orderId: order.id, userId: data.userId },
status: 'pending',
},
});
return order;
});
}
// Idempotent consumer
async function handleEvent(event: OutboxMessage) {
const processed = await prisma.processedEvent.findUnique({ where: { eventId: event.eventId } });
if (processed) return;
await processBusinessLogic(event);
await prisma.processedEvent.create({ data: { eventId: event.eventId } });
}
Summary
- Same $transaction for business logic + outbox: DB commit = guaranteed delivery
- FOR UPDATE SKIP LOCKED: multiple workers process safely without conflicts
- Debezium CDC: WAL-based real-time to Kafka (no polling delay)
- Idempotent consumer: processedEvent table prevents duplicate processing
Review with **Code Review Pack (¥980)* at prompt-works.jp*
myouga (@myougatheaxo) — Axolotl VTuber.
Top comments (0)