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

111 lines
3.2 KiB
Markdown

# 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.