Files
prompts/docs/skills/fastapi-async-sqlalchemy-modernization/references/transactions.md
T
John Lancaster 3347443ca9 formatting
2026-06-19 01:29:05 -05:00

3.2 KiB

Async Transaction Boundaries

!!! info "Primary sources" - SQLAlchemy transactions - SQLAlchemy asyncio extension - SQLAlchemy connections

??? abstract "Decision metadata" - Status: adopted - Decision level: mandatory - Applies to: api-runtime, workers, tests - Last reviewed: 2026-06-17


Purpose

Define consistent transaction demarcation for async SQLAlchemy so write behavior is predictable, rollback semantics are clear, and concurrent request flows remain safe.


Scope and Non-Goals

  • In scope: transaction ownership, write/read policy, exception and rollback behavior, nested transaction guidance.
  • Out of scope: business-domain validation rules and cross-service distributed transactions.

Rules

  • Every mutating use case must run inside an explicit transaction boundary.
  • Prefer async with session.begin(): for write units.
  • Keep transaction ownership at service/use-case boundary, not deep in helper internals.
  • Read paths should not auto-upgrade into hidden write behavior.
  • On exception in a transaction block, rely on rollback semantics and propagate or map exceptions intentionally.

Pattern A: Single write unit

async def create_order(session: AsyncSession, payload: OrderIn) -> Order:
    async with session.begin():
        order = Order(...)
        session.add(order)
        # additional writes...
    return order

Pattern B: Explicit read flow

async def get_order(session: AsyncSession, order_id: UUID) -> Order | None:
    stmt = select(Order).where(Order.id == order_id)
    return await session.scalar(stmt)

Pattern C: Nested transaction (only when required)

async with session.begin():
    # outer transaction
    async with session.begin_nested():
        # savepoint-scoped operation
        ...

Use nested transactions only when partial failure semantics are explicitly required.


Exception and Rollback Policy

  • Write block fails: transaction context rolls back.
  • Caller decides whether to translate exception (for example to domain/API errors).
  • Do not swallow DB exceptions silently; map or re-raise intentionally.

Anti-Patterns

  • Multiple commits scattered across one logical use case.
  • Helper functions that commit/rollback without caller awareness.
  • Mixing implicit and explicit transaction styles in confusing ways.
  • Using savepoints as a default pattern rather than a targeted tool.

Operational Checks

  • All mutating service functions declare one clear transaction boundary.
  • No repository/helper performs hidden commit calls.
  • Transaction style is consistent across handlers and workers.

Testing Checks

  • Success path test verifies expected durable writes.
  • Failure path test verifies rollback behavior.
  • Tests cover concurrency-sensitive write flows.
  • Savepoint usage (if present) has dedicated behavior tests.

Migration Notes

  • First stabilize session scope, then normalize transaction ownership.
  • Replace ad hoc commit patterns incrementally with bounded write units.