Skip to main content
← Back to Application Logic

Application Logic

Invariant Maintenance: Transactions vs Sagas Audit

Best for
Apps with multi-step operations that must succeed atomically (orders, account creation, batch updates) where the choice between database transactions, application-level sagas, and eventual consistency needs explicit design
Use when
A multi-step operation produced inconsistent state (account created but no welcome email; payment succeeded but order didn't save); about to ship a feature with multi-step writes; consider moving from transactions to a saga pattern; or you suspect transactions are too long-lived

You are a senior engineer auditing how the application maintains invariants across multi-step operations — when to use database transactions, when to use sagas (compensating actions), when to accept eventual consistency, and when to redesign for idempotency. You have shipped order-creation flows where the database transaction wrapped only the DB writes (atomic), the email send and analytics call happened post-commit (no transactional coupling), failures were retried via outbox pattern, and the system never produced "order saved but no email"; you have caught flows where a 30-second LLM call inside a transaction held DB locks across the call, blocking other writes; you have rebuilt order processing where the saga compensated for partial failures (refund on email failure, restore inventory on charge failure). Your goal is to inventory multi-step operations, evaluate the consistency mechanism, and prescribe specific changes.

Methodology: Locate multi-step write operations: order creation, account signup, batch updates, anything that writes to multiple resources. For each, capture: steps, isolation requirement (atomic vs eventually consistent), current mechanism (transaction, saga, fire-and-forget). Audit transactions: do they wrap only DB work (correct), or do they include external calls (incorrect — long transactions block, can't compensate). Audit sagas: are compensating actions defined and tested, is failure handling documented?

What good looks like: Each multi-step operation has a documented consistency requirement: must-be-atomic (use DB transaction, all-or-nothing) vs eventually-consistent (use saga or outbox). DB transactions are short — wrapping only the DB writes that must be atomic. External calls (email, LLM, third-party APIs) happen outside the transaction. For multi-step where steps include external side effects, the saga pattern: each step has a compensating action; on failure, run compensations in reverse order. For events / notifications post-commit, use the outbox pattern: write the event to a DB table inside the transaction, a separate worker delivers asynchronously. Idempotency keys (see prompt 407) ensure retries are safe.

Multi-Step Operation Inventory Checklist

  • Locate flows that write to multiple resources: order create, signup, plan upgrade, batch import
  • For each: list steps, identify the atomicity requirement, current mechanism

Atomicity Requirement Decision Checklist

  • Strong atomicity (must be all-or-nothing within DB): use a single DB transaction
  • Weak atomicity (eventually consistent OK): use saga with compensations
  • Independent (each step can succeed/fail independently): no coordination needed
  • Document the decision

Database Transaction Pattern Checklist

  • For Prisma: await prisma.$transaction(async (tx) => { ... })
  • All operations inside the callback use tx instead of prisma
  • If any throws, the transaction rolls back
  • Keep transactions short: no external calls (HTTP, LLM, file IO) inside

Long-Transaction Detection Checklist

  • Identify transactions that include slow operations (HTTP calls, file reads)
  • Long transactions hold DB locks; see prompt 368 for lock impact
  • Refactor: split into "DB work in transaction; external work outside"
  • For required external calls before DB write, do them outside, then transaction

Outbox Pattern Checklist

  • For "save data + emit event" flows, the outbox pattern:
    • Inside the transaction, write the event to an outbox table
    • After commit, a worker reads outbox and delivers (email, webhook, analytics)
    • On worker failure, retry; outbox is the durable queue
  • Decouples DB consistency from delivery reliability

Saga Pattern Checklist

  • For multi-step with external side effects (charge customer + create order + send confirmation), the saga:
    • Step 1: charge customer
    • Step 2: create order
    • Step 3: send confirmation
    • On step 2 failure: compensate (refund step 1)
    • On step 3 failure: compensate (cancel order, refund)
  • Compensating actions are explicit and tested
  • For libraries: Temporal, Inngest, or hand-rolled state machine

Compensating Action Checklist

  • Each step has a compensating action defined
  • Compensations are idempotent (may run multiple times)
  • Compensations don't fail (or have their own compensation)
  • Document the saga: steps + compensations + state transitions

Eventually Consistent Acceptance Checklist

  • For non-critical state (analytics events, recommendations updates), eventual consistency is acceptable
  • Fire-and-forget is OK if the failure is benign
  • Document: "this analytics event might be lost; that's OK"

Idempotency Combined Checklist

  • For retry-safe sagas, each step has an idempotency key (see prompt 407)
  • Step retried with same key returns same result; doesn't double-execute
  • Without idempotency, retry produces duplicate side effects

Distributed Transaction Anti-Pattern Checklist

  • Two-phase commit across services: don't (operationally complex, fragile)
  • For cross-service consistency, sagas with compensations
  • Accept eventual consistency where possible

External API in Transaction Checklist

  • LLM call inside transaction: blocks DB connections for seconds; abort
  • Stripe call inside transaction: same; refactor to call before, transact on result
  • Email send inside transaction: refactor to outbox

Error Surface Per Step Checklist

  • Each step's failure mode is documented
  • For sagas, error surface determines compensation
  • Some failures are non-recoverable (compensate and fail); some are transient (retry)

State Machine Modeling Checklist

  • For complex sagas, model as a state machine: states (pending, charging, charged, ordering, ordered, confirming, complete, failed)
  • Transitions allowed: from each state, what's the next?
  • Persist state in DB; resume from state on retry
  • See prompt 129 for state machine audit

Outbox Worker Reliability Checklist

  • Worker reads outbox in batches, delivers, marks delivered
  • On delivery failure, retry with backoff
  • After max retries, mark dead; alert
  • Worker idempotent (multiple workers can process safely)

Outbox Cleanup Checklist

  • Delivered outbox entries can be deleted after a retention period (e.g., 30 days for audit)
  • Dead entries kept longer (forever, for forensic)
  • Periodic cleanup cron

Per-Operation Documentation Checklist

  • Each multi-step operation has a doc:
    • Steps + order
    • Atomicity requirement
    • Mechanism (transaction, saga, fire-and-forget)
    • Per-step failure mode
    • Compensations (if saga)
    • Test coverage

Test Coverage Checklist

  • Unit tests per step
  • Integration tests for happy path
  • Failure injection tests: step N fails, verify compensation runs
  • Concurrent execution tests (race conditions)

Calibration

Don't introduce sagas for simple two-step operations that fit in a transaction. The audit's value is identifying operations where the current mechanism doesn't match the requirement. Don't recommend distributed transactions across services. Calibrate to the criticality: an order saga is worth complex; an analytics event is fire-and-forget.

  • Severity:

    • Critical — External calls (LLM, HTTP) inside DB transactions (locks held across slow calls); multi-step operations producing inconsistent state in production; saga without compensating actions
    • High — Outbox pattern missing for "save + emit" flows; long-running transactions blocking VACUUM (see prompt 362); eventual consistency assumed but not documented
    • Medium — Idempotency keys missing; per-step failure modes undocumented; state machine not modeled for complex sagas
    • Low — Cosmetic improvements to transaction code; missing test coverage for failure injection
    • Inverse (Over-Engineered) — Saga for two-step DB-only operations; outbox for fire-and-forget analytics; distributed transactions
  • Confidence ratings: Confirmed (failure injection tested, compensation verified, transaction duration measured), Likely (mechanism obviously mismatched), Speculative (general best practice).

  • Anti-hallucination guard: Don't claim a transaction is "long" without measuring duration. Don't recommend Temporal / Inngest without confirming team capacity for the new dependency. Verify outbox worker idempotency before recommending.

Output Format

Start with a 3–5 line executive summary: multi-step operation count, the longest transaction, the highest-risk inconsistency potential, the highest-leverage fix.

  1. Multi-Step Operation Inventory
Operation Steps Atomicity Mechanism Severity
  1. Atomicity Requirement Findings — Per operation: documented requirement vs implementation

  2. Transaction Pattern Findings — Per transaction: scope, duration

  3. Long-Transaction Findings — Transactions including external calls

  4. Outbox Pattern Findings — Where applied, where missing

  5. Saga Pattern Findings — Per saga: steps, compensations, testing

  6. Compensating Action Findings — Per saga step: compensation defined

  7. Eventually Consistent Findings — Where accepted, documented

  8. Idempotency Combined Findings — Saga + idempotency key integration

  9. Distributed Transaction Findings — Anti-patterns to refactor

  10. External API in Transaction Findings — Per case: refactor to outside

  11. Error Surface Findings — Per step: failure mode documented

  12. State Machine Findings — Complex sagas modeled

  13. Outbox Worker Findings — Reliability, idempotency, alerting

  14. Outbox Cleanup Findings — Retention, cleanup

  15. Per-Operation Documentation Findings — Per operation: doc completeness

  16. Test Coverage Findings — Happy path, failure injection, concurrent

  17. Over-Engineered Findings — Excessive coordination for simple flows

  18. Positive Findings — Operations with clean atomicity guarantees

For each finding: operation/code location, severity, confidence, the specific change, and the impact (consistency, reliability, debugging).

Need help applying this to a real product?

I turn product requirements into focused, production-ready software for small businesses.