Skip to main content

ADR-0001: Transactional outbox for side effects

Status​

Accepted. Reconstructed from the implementation and from AGENTS.md. The original discussion was not in the repository.

Context​

Order creation must record fulfillment work and a wallet debit, and stock changes must reach channels. Those effects cannot run only as in-process events on the API. The API process does not load the outbox dispatcher, and an in-process emit is lost if the process stops.

Decision​

Writers call OutboxWriterService.enqueue with the Prisma transaction client. The worker’s OutboxDispatcherService claims rows and emits the event type through EventEmitter2 inside tenant context.

Bull remains a second mechanism for work that is queued directly (sync, labels, email, inventory flush, webhooks).

Alternatives​

Not recorded. The workspace README sentence that says events are dispatched via Bull workers does not match the dispatcher, which polls OutboxEvent. Both mechanisms exist. The outbox is not a Bull queue.

Consequences​

  • A throw before commit rolls the business row and the outbox row back together.
  • A throw in a listener retries the event, up to 10 times, then DEAD. Listeners that are not idempotent can double-apply.
  • Emitting from order command with EventEmitter2 skips this path. AGENTS.md forbids that for order side effects.
  • There is no replay tool in the code that was read.