SQLAlchemy 2.0
Using SQLAlchemy well is less about memorizing ORM syntax and more about separating `Session`, transaction ownership, loading strategy, and API boundaries. In web services, most complexity comes from getting those boundaries wrong.
Quick takeaway: treat `Session` as a unit-of-work and transaction boundary, not as a cache or a global DB handle. Repositories talk to the session, but commits belong at the use-case boundary.
The Questions That Matter Most
What is a Session?
It is a short-lived work context that combines identity mapping and unit-of-work behavior.
Who owns commit?
The use case or service layer should own transaction completion, not the repository.
Can ORM entities cross the API boundary?
Usually they should not. Lazy loading, serialization, and schema evolution become tightly coupled.
When is async worth it?
When your whole stack is async and high-concurrency I/O matters. Session-sharing rules become stricter, not looser.
How should settings change by deployment target?
Lambda, Kubernetes, workers, and batch jobs do not share the same process lifetime or connection budget. Copy-pasting one pool configuration is a fast way to overload the database.
How do migrations stay safe?
You need Alembic revision-graph discipline plus additive rollout, backfill, and contract sequencing to avoid downtime.
Recommended Reading Order
- Session and Unit of Work
- Deployment and Engine Settings
- Relationships and Loading
- Async SQLAlchemy
- Core vs ORM
- Migrations and Patterns
- Alembic and Zero-Downtime Migrations
Working Rules for Real Services
- Keep sessions scoped to a request or use case.
- Do not
commit()inside repositories. - Match engine and pool settings to the deployment process model.
- Design read and write paths differently.
- Do not collapse ORM entities, Pydantic schemas, and domain concepts into one class.
- In async code, never share an
AsyncSessionacross concurrent tasks. - Split destructive migrations into additive rollout and contract phases.