# Async Transaction Boundaries !!! info "Primary sources" - [SQLAlchemy transactions](https://docs.sqlalchemy.org/en/21/orm/session_transaction.html) - [SQLAlchemy asyncio extension](https://docs.sqlalchemy.org/en/21/orm/extensions/asyncio.html) - [SQLAlchemy connections](https://docs.sqlalchemy.org/en/21/core/connections.html) ??? 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. --- ## Recommended Patterns ### Pattern A: Single write unit ```python 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 ```python 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) ```python 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.