ProgramGuru

Transactional Outbox Pattern

Transactional Outbox: start from the problem

See the responsibilities

Local transactionWrites order and outbox intent
Committed outboxContains stable event identity
RelayPublishes and tracks progress
ConsumerHandles possible repeat delivery
Local transaction: Writes order and outbox intent → Committed outbox: Contains stable event identity → Relay: Publishes and tracks progress → Consumer: Handles possible repeat delivery

An order is saved, then the application publishes an event. A crash between those steps leaves a saved order with no event. Publishing first creates the opposite risk: consumers hear about an order that did not commit.

This is a data and messaging pattern lesson. Follow the responsibilities, inspect the small JavaScript example and compare the design choices. The example is local teaching code; its boundary is explained before you apply the idea to a real service.

What you’ll learn

  • Explain the problem Transactional Outbox addresses and the roles involved.
  • Follow the example and distinguish this pattern from nearby alternatives.
  • Choose when the benefit justifies the extra responsibilities and testing.

How the responsibilities work

Transactional Outbox records the business change and publication intent in the same supported local transaction. A relay later publishes committed outbox entries. That removes the need for a single atomic transaction across the database and broker, while adding durable relay work.

The code models one state replacement containing both records. It is not a real transactional database. A database implementation must use its supported atomic boundary, and the relay must account for publication followed by a lost acknowledgement.

Follow a small example

Read the JavaScript from top to bottom and compare the expected output. The values are chosen to make this pattern’s responsibility visible. This example runs in one local execution context; it does not create remote services, durable storage or production access controls.

JavaScript · local teaching example

let state = {orders: [], outbox: []};
function commitOrder(id) {
  const event = {id: 'event-' + id, orderId: id};
  state = {orders: [...state.orders, id], outbox: [...state.outbox, event]};
}
commitOrder('O1');
console.log(state.orders[0] + ' | ' + state.outbox[0].id);

Expected output

O1 | event-O1

Choose a design and inspect the consequence

Compare the design choices

A relay stops after publishing but before marking progress.
The business data and outbox are in unrelated stores.

Compare the visible consequences. With JavaScript enabled, choose an option in each scenario to assemble your decision record.

This is an exploratory design exercise. It does not run infrastructure, measure capacity or replace the lesson’s quiz.

Use the consequences to explain your decision, including the simpler alternative.

Apply the pattern to two situations. Select one option in each situation to build a decision record. Every option and its consequence stays visible, and you can change your choices.

When it helps—and what it costs

Use it when a local data change must reliably lead to later messaging and both local records can share an appropriate transaction.

Relays can publish duplicates if they crash before recording success. Define event identity, consumer idempotency, ordering scope and retention. Multiple database owners cannot be made atomic simply by putting one outbox in one of them.

Distinguish the neighbouring patterns

Outbox records publication intent. Event Sourcing makes events authoritative history. Saga coordinates separate local business transactions; it can use outboxes within its participants.

Follow the existing outbox explanation, authoritative event history, separate-owner recovery to compare the specific responsibility before combining patterns.

For further reading, use AWS’s Transactional Outbox guidance. Compare it with this original example and the guarantee required by your application.

Check your pattern reasoning

What must share the supported local transaction?
Choose one answer. You can try again.

Answer and explanation

The business write and its outbox intent.

The committed record cannot be separated from the intended event.

Check your pattern reasoning

Why can the relay publish an event twice?
Choose one answer. You can try again.

Answer and explanation

It can publish successfully but lose or fail to record acknowledgement.

Consumer effects still need a repeated-delivery rule.

Compare the pattern boundaries

The relay publishes, then crashes before recording delivery progress. Which result must consumers be prepared for?
Choose one answer. You can try again.

Answer and explanation

Repeat publication of the same identified event.

The intent is durable but relay acknowledgement can be uncertain.

Name the responsibility before choosing the pattern

For Transactional Outbox, describe the problem, the participating roles and the observable contract. Use the example as a starting point and test the boundaries identified in the trade-offs. A pattern name alone does not establish correctness or performance.

Return to the design-pattern guide to compare families, or apply the idea alongside separate-owner recovery.

Reaching the recap and completing the quizzes are separate milestones.

    Next lesson: CQRS Pattern →