123 lines
3.9 KiB
Markdown
123 lines
3.9 KiB
Markdown
# SQLModel Adoption and Boundaries
|
|
|
|
!!! info "Primary sources"
|
|
- [SQLModel documentation](https://sqlmodel.tiangolo.com/)
|
|
- [SQLModel FastAPI session dependency tutorial](https://sqlmodel.tiangolo.com/tutorial/fastapi/session-with-dependency/)
|
|
- [SQLModel release notes](https://sqlmodel.tiangolo.com/release-notes/)
|
|
- [SQLAlchemy asyncio extension](https://docs.sqlalchemy.org/en/21/orm/extensions/asyncio.html)
|
|
|
|
??? abstract "Decision metadata"
|
|
- Status: adopted
|
|
- Decision level: advisory
|
|
- Applies to: api-runtime, workers, tests
|
|
- Last reviewed: 2026-06-26
|
|
|
|
---
|
|
|
|
## Purpose
|
|
|
|
Define when and how to use SQLModel in an async FastAPI + SQLAlchemy modernization effort.
|
|
|
|
The goal is pragmatic adoption: use SQLModel where it reduces model duplication and improves typing ergonomics, without disrupting established async engine/session lifecycle rules.
|
|
|
|
---
|
|
|
|
## Scope and Non-Goals
|
|
|
|
- In scope: model-layer decisions, integration boundaries, phased adoption strategy.
|
|
- Out of scope: full framework rewrites and all-at-once model migration.
|
|
|
|
---
|
|
|
|
## Rules
|
|
|
|
- Keep SQLAlchemy async primitives as the runtime base: `create_async_engine`, `async_sessionmaker`, and `AsyncSession`.
|
|
- Prefer SQLModel for new domain modules where table models and API schemas would otherwise be duplicated.
|
|
- Migrate by bounded module or feature area; do not force whole-repo conversion in one phase.
|
|
- Keep transaction and session ownership policies identical whether models are SQLAlchemy Declarative or SQLModel.
|
|
- Document explicit reasons when SQLModel is deferred for a module.
|
|
|
|
---
|
|
|
|
## Recommended Patterns
|
|
|
|
### Pattern A: Bounded module adoption
|
|
|
|
- Choose one feature slice (for example, billing, projects, or auth profile data).
|
|
- Introduce SQLModel models for that slice only.
|
|
- Keep unchanged modules on existing SQLAlchemy models until a dedicated migration phase.
|
|
|
|
### Pattern B: Data model split for API boundaries
|
|
|
|
Use distinct models for persistence and external contracts.
|
|
|
|
```python
|
|
from sqlmodel import Field, SQLModel
|
|
|
|
|
|
class UserBase(SQLModel):
|
|
email: str
|
|
display_name: str
|
|
|
|
|
|
class User(UserBase, table=True):
|
|
id: int | None = Field(default=None, primary_key=True)
|
|
|
|
|
|
class UserCreate(UserBase):
|
|
pass
|
|
|
|
|
|
class UserRead(UserBase):
|
|
id: int
|
|
```
|
|
|
|
### Pattern C: Keep async lifecycle unchanged
|
|
|
|
```python
|
|
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
|
|
|
|
engine = create_async_engine(settings.database_url, pool_pre_ping=True)
|
|
session_factory = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
|
|
```
|
|
|
|
---
|
|
|
|
## Interoperability Notes
|
|
|
|
- SQLModel is designed as a thin layer over SQLAlchemy and Pydantic, so mixed codebases are expected during migration.
|
|
- Prefer one query style per module to reduce cognitive overhead.
|
|
- Keep loader strategies explicit in async paths to avoid implicit I/O surprises.
|
|
|
|
---
|
|
|
|
## Anti-Patterns
|
|
|
|
- Treating SQLModel adoption as equivalent to async-session modernization.
|
|
- Rewriting all models at once without rollback checkpoints.
|
|
- Introducing SQLModel in handlers while keeping old global/shared session patterns.
|
|
- Mixing multiple query/session idioms within the same module without clear conventions.
|
|
|
|
---
|
|
|
|
## Operational Checks
|
|
|
|
- Modernized module documents whether it is SQLModel-first or SQLAlchemy-only.
|
|
- Session/transaction ownership remains consistent across both model styles.
|
|
- New model modules use explicit API boundary models where needed.
|
|
|
|
---
|
|
|
|
## Testing Checks
|
|
|
|
- Module-level tests verify CRUD semantics for adopted SQLModel models.
|
|
- API tests verify response/request model behavior for SQLModel-based endpoints.
|
|
- Regression tests confirm unchanged modules continue to function during phased rollout.
|
|
|
|
---
|
|
|
|
## Migration Notes
|
|
|
|
- Start with low-risk bounded domains.
|
|
- Expand only after validation of session lifecycle, transaction behavior, and endpoint correctness.
|
|
- Maintain a tracked backlog of deferred modules with rationale and planned phase. |