3.1 KiB
3.1 KiB
Async Transaction Boundaries
Source:
- https://docs.sqlalchemy.org/en/21/orm/session_transaction.html
- https://docs.sqlalchemy.org/en/21/orm/extensions/asyncio.html
- https://docs.sqlalchemy.org/en/21/core/connections.html
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
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.