more
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
# 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
|
||||
|
||||
```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.
|
||||
Reference in New Issue
Block a user