You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

7.0 KiB

SQLModel ORM 技术选型方案

文档版本:v1.0
创建日期:2025-01-27
决策状态 已采纳


背景

Jointo项目后端采用 FastAPI 框架,需要选择合适的 ORM 工具来管理数据库操作。原计划使用 SQLAlchemy 2.0,但在技术评审中发现 SQLModel 更适合本项目。


问题分析

传统 SQLAlchemy 的痛点

  1. 重复代码:需要分别定义 ORM Model 和 Pydantic Schema
  2. 类型安全不足:需要手动维护类型提示
  3. 开发效率低:模型变更需要同步修改多处
  4. 学习曲线:新手需要理解 SQLAlchemy 和 Pydantic 两套体系

示例对比

SQLAlchemy 方式(需要定义两次)

# models/project.py
from sqlalchemy import Column, String, DateTime
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class ProjectModel(Base):
    __tablename__ = "projects"
    id = Column(String, primary_key=True)
    name = Column(String, nullable=False)
    description = Column(String)
    created_at = Column(DateTime)

# schemas/project.py
from pydantic import BaseModel
from typing import Optional
from datetime import datetime

class ProjectCreate(BaseModel):
    name: str
    description: Optional[str]

class ProjectResponse(BaseModel):
    id: str
    name: str
    description: Optional[str]
    created_at: datetime

SQLModel 方式(只需定义一次)

# models/project.py
from sqlmodel import SQLModel, Field
from typing import Optional
from datetime import datetime

class ProjectBase(SQLModel):
    name: str = Field(max_length=255)
    description: Optional[str] = None

class Project(ProjectBase, table=True):
    """数据库表模型"""
    id: Optional[str] = Field(default=None, primary_key=True)
    created_at: Optional[datetime] = Field(default_factory=datetime.utcnow)

class ProjectCreate(ProjectBase):
    """创建请求模型"""
    pass

class ProjectRead(ProjectBase):
    """读取响应模型"""
    id: str
    created_at: datetime

方案对比

方案 1:SQLModel(推荐)

技术特点

  • FastAPI 作者(Sebastián Ramírez)开发
  • 基于 SQLAlchemy 2.0 + Pydantic v2
  • 单一模型定义,同时用于 ORM 和 API Schema

优势

  • 类型安全:完整的 TypeScript 级别类型提示
  • 减少 50% 代码:不需要重复定义 Model 和 Schema
  • 完美集成:与 FastAPI 无缝配合
  • 底层稳定:继承 SQLAlchemy 2.0 的所有功能
  • 异步支持:原生 async/await
  • 学习曲线低:统一的 API 设计

劣势

  • ⚠️ 版本较新(0.0.14),但已在生产环境广泛使用
  • ⚠️ 社区规模小于 SQLAlchemy(但增长迅速)

适用场景

  • 新项目(本项目)
  • 需要类型安全的项目
  • FastAPI 项目

方案 2:SQLAlchemy 2.0(保守方案)

技术特点

  • Python 最成熟的 ORM
  • 功能强大,支持复杂查询

优势

  • 社区最大,文档最全
  • 生产环境验证充分
  • 支持所有高级特性

劣势

  • 需要重复定义 Model 和 Schema
  • 类型安全需要手动维护
  • 开发效率较低

适用场景

  • 大型企业项目,稳定性优先
  • 需要使用 SQLAlchemy 高级特性

方案 3:Tortoise ORM

技术特点

  • 纯异步设计
  • 类似 Django ORM

优势

  • 性能优秀
  • 学习曲线低

劣势

  • 仍需分别定义 Model 和 Schema
  • 社区规模小
  • 功能不如 SQLAlchemy 强大

适用场景

  • 简单 CRUD 应用

决策结果

选择方案 1:SQLModel

决策理由

  1. 与 FastAPI 完美集成:同一作者开发,API 设计一致
  2. 提升开发效率:减少 50% 重复代码
  3. 类型安全:编译时类型检查,减少运行时错误
  4. 底层稳定:基于 SQLAlchemy 2.0,继承其稳定性
  5. 支持复杂查询:项目、分镜、资源等复杂关联查询
  6. 异步支持:与 FastAPI 的异步特性匹配
  7. 迁移成本低:完全兼容 Alembic 迁移工具

风险评估

风险 等级 应对措施
版本较新 底层是 SQLAlchemy 2.0,稳定性有保障
社区规模 FastAPI 作者维护,社区活跃度高
功能缺失 可以直接使用 SQLAlchemy 的高级特性

实施计划

1. 依赖安装

pip install sqlmodel==0.0.14
pip install alembic==1.12.0

2. 项目结构调整

app/
├── models/          # SQLModel 模型(同时用于 ORM 和 API)
│   ├── project.py
│   ├── storyboard.py
│   └── ...
├── schemas/         # 额外的请求/响应模型(可选)
│   └── common.py

3. 数据库连接配置

# app/core/database.py
from sqlmodel import create_engine, Session

engine = create_engine(
    settings.DATABASE_URL,
    echo=True,
    pool_pre_ping=True
)

def get_session():
    with Session(engine) as session:
        yield session

4. 模型定义示例

# app/models/project.py
from sqlmodel import SQLModel, Field, Relationship
from typing import Optional, List
from datetime import datetime

class Project(SQLModel, table=True):
    __tablename__ = "projects"

    id: Optional[str] = Field(default=None, primary_key=True)
    name: str = Field(index=True, max_length=255)
    description: Optional[str] = None
    type: str  # 'mine' | 'collab'
    created_at: Optional[datetime] = Field(default_factory=datetime.utcnow)
    updated_at: Optional[datetime] = Field(default_factory=datetime.utcnow)

    # 关联关系
    storyboards: List["Storyboard"] = Relationship(back_populates="project")

5. API 使用示例

# app/api/v1/projects.py
from fastapi import APIRouter, Depends
from sqlmodel import Session
from app.models.project import Project
from app.core.database import get_session

router = APIRouter()

@router.post("/projects", response_model=Project)
async def create_project(
    project: Project,  # 直接使用 SQLModel
    session: Session = Depends(get_session)
):
    session.add(project)
    await session.commit()
    await session.refresh(project)
    return project  # 自动序列化

迁移路径

如果未来需要迁移回 SQLAlchemy:

  1. SQLModel 底层就是 SQLAlchemy,迁移成本极低
  2. 只需修改模型定义,查询 API 基本一致
  3. 可以逐步迁移,不需要一次性重写

参考资料


变更记录

v1.0 (2025-01-27)

  • 初始版本
  • 确定采用 SQLModel 作为 ORM 方案
  • 完成技术对比和实施计划

决策人:后端架构团队
审批状态 已批准
生效日期:2025-01-27