Menu

Topic 2 of 8

Transaction Boundaries

Learn Transaction Boundaries for free with explanations, exercises, and a quick test (for API Engineer).

Published: January 21, 2026 | Updated: January 21, 2026

Why this matters

As an API Engineer, you often perform operations that must be all-or-nothing: create an order, reserve inventory, charge a card, and notify other services. Transaction boundaries define exactly where those guarantees start and end, so your system avoids double charges, ghost orders, and inconsistent reads.

  • Design endpoints that update multiple tables safely.
  • Decide what belongs inside a database transaction vs. what must happen after commit.
  • Coordinate across services using patterns like outbox and sagas.

Quick test is available to everyone; only logged-in users get saved progress.

Concept explained simply

A transaction boundary is the invisible fence around steps that must succeed or fail together. Inside the fence: strong guarantees (atomicity, consistency, isolation, durability). Outside the fence: best-effort, retries, compensation, and idempotency.

Mental model

Imagine an airlock:

  • Inside the airlock (transaction): everything is locked until you either commit (open the outer door) or rollback (go back).
  • Outside the airlock: you can send messages, call other services, and handle retries. Those actions must be safe to perform more than once.
Step 1: Make all required local DB changes in one short transaction.
Step 2: Commit. This is your transaction boundary.
Step 3: After commit, publish events or call external services with idempotency.
Key rules to remember
  • Do not call external services within a long-running DB transaction.
  • Keep transactions short and scoped to one database when possible.
  • For cross-service workflows, use local transactions + reliable messaging (outbox) and compensation (sagas).

Worked examples

Example 1 — Single-service order creation

Goal: Insert Order and OrderItems atomically.

BEGIN;
INSERT INTO orders(id, user_id, status) VALUES($1, $2, 'NEW');
INSERT INTO order_items(order_id, sku, qty) VALUES ...;
COMMIT; -- Transaction boundary
  • Inside boundary: All order rows.
  • Outside boundary: Notify "order.created" event to other services.
  • Why: If items fail to insert, rollback ensures no half-order exists.
Example 2 — Outbox: order + event publish

Problem: You must publish "order.created" exactly once.

BEGIN;
-- 1) Insert order
INSERT INTO orders(...);
-- 2) Insert outbox record (status = 'PENDING')
INSERT INTO outbox(id, topic, payload, status) VALUES(..., 'order.created', ..., 'PENDING');
COMMIT; -- Boundary: order + outbox saved atomically

-- Separate background worker (outside transaction):
-- Fetch PENDING outbox rows, publish to broker, mark as SENT
  • Guarantee: Either both order and outbox row exist, or neither. Publishing is retried until success.
  • Idempotency: Consumer uses message ID with a unique constraint to ignore duplicates.
Example 3 — Distributed saga: reserve inventory, then capture payment

Workflow with 3 services: Orders, Inventory, Payments.

  1. Orders: Local tx creates order + outbox event "order.created"; commit.
  2. Inventory: On event, local tx reserves stock; commit; publish "stock.reserved".
  3. Payments: On event, authorize payment; commit; publish "payment.authorized".

If any step fails:

  • Compensation: If payment fails, Inventory releases reservation. Orders marks order as "FAILED".
  • Boundaries: Each service commits locally; there is no global distributed transaction.

Boundary checklist

  • Is every DB change required to be all-or-nothing inside a single short transaction?
  • Are external calls moved after commit (or handled with outbox)?
  • Do you enforce idempotency with a unique key or token?
  • Are timeouts and retries defined outside the transaction?
  • Do you have compensating actions for cross-service failures?

Exercises

These mirror the graded exercises below. Try them here first.

Exercise 1 — Signup + welcome email (define the boundary)

You have a signup endpoint that creates a user and sends a welcome email. Design the steps and mark the exact transaction boundary. Use an outbox if needed. Describe:

  • What goes inside the DB transaction
  • What happens after commit
  • How you guarantee at-least-once delivery and idempotent email sending
Exercise 2 — Money transfer (prevent double spend)

You implement a transfer: debit Wallet A, credit Wallet B, and write a ledger entry. Sometimes the same request is retried. Design:

  • The local transaction steps
  • The idempotency mechanism (schema or constraints)
  • What isolation/locking you use to prevent overspending
  • If split across services, how you ensure consistency

Common mistakes and self-check

Mistakes to avoid
  • Calling external services inside a long DB transaction, causing locks and timeouts.
  • Publishing messages before commit, risking orphan events.
  • No idempotency key on create endpoints; duplicates on retries.
  • Overly broad transactions that include unrelated work.
  • Assuming "exactly once" across services; rely on at-least-once + idempotency.
Self-check
  • Can you point to the exact COMMIT point for each workflow?
  • Can your service be safely retried at any step without double effects?
  • Are schema constraints backing your idempotency (unique keys)?
  • Do you know which operations hold locks and for how long?

Practical projects

  • Transactional outbox demo: Create an order service with an outbox table and a background publisher. Kill the publisher mid-run and verify eventual delivery.
  • Saga with compensation: Order → Inventory → Payment. Force failures and validate compensations restore consistency.
  • Idempotent endpoints: Implement POST /transfers using an Idempotency-Key header and a unique index to avoid duplicates.

Learning path

  • Prerequisites: HTTP basics, relational DB fundamentals, ACID, simple SQL transactions.
  • Next: Outbox pattern details, Sagas/orchestration vs. choreography, exactly-once myth, idempotent producers/consumers, isolation levels and locking.

Who this is for

  • API Engineers building endpoints that coordinate multiple state changes.
  • Backend devs moving from monolith to microservices.
  • Engineers responsible for reliable event-driven integrations.

Prerequisites

  • Comfortable with SQL (INSERT/UPDATE/DELETE, transactions).
  • Basic understanding of message queues/events.
  • Familiarity with HTTP retries and timeouts.

Mini challenge

Design the boundary for a "refund" flow: mark order as REFUND_PENDING, publish event, call payment gateway to refund, and finalize status. Write the steps, highlight the commit point, and define how retries cannot issue multiple refunds.

Next steps

  • Refactor one of your endpoints to use a transactional outbox.
  • Add idempotency keys and unique constraints to your create operations.
  • Instrument logs with correlation IDs to trace a request across services.

Practice Exercises

2 exercises to complete

Instructions

Design a safe signup workflow:

  • Inside the DB transaction: which rows are written?
  • After commit: how do you deliver a welcome email reliably?
  • How do you ensure at-least-once delivery and prevent duplicate emails on retries?

Describe the sequence as bullet points and name the exact transaction boundary.

Expected Output
Boundary at DB COMMIT where user row and outbox/email-job row are inserted atomically. After commit, a worker publishes/sends email using the outbox, with idempotency (unique message ID) to avoid duplicates.

Transaction Boundaries — Quick Test

Test your knowledge with 8 questions. Pass with 70% or higher.

8 questions70% to pass

Have questions about Transaction Boundaries?

AI Assistant

Ask questions about this tool