全栈架构:三套 Schema
在一个数据驱动的全栈系统中,最核心的工作流莫过于:前端发送请求 -> 后端处理逻辑 -> 读写数据库 -> 数据返回前端。
在这个过程中,同一个业务实体(比如一只“动物”或一朵“花”),虽然代表的信息是一致的,但在不同的系统层级中,其表现形式(Schema)和承载的职责是截然不同的。
通常,一个规范的全栈项目需要维护“三套 Schema”:
- Database Schema:用于数据库存储(ORM 模型)。
- API Schema:用于后端接口的数据验证与序列化(Pydantic 模型)。
- Frontend Schema:用于前端页面的类型检查与展示(TypeScript 接口)。
以 Python (FastAPI/SQLAlchemy) + Frontend (TypeScript) 为例,梳理这三套 Schema 的定义与协作。
第一套:Database Schema (ORM Layer)
数据库层是数据的源头。在 Python 后端中,我们通常使用 SQLAlchemy 这样的 ORM(对象关系映射)库,将数据库表结构映射为 Python 类。通常在 backend/app/db 目录下维护数据库连接逻辑。
- Engine: 负责与数据库的实际通信。
- Session: 数据库会话,相当于一个“连接句柄”,用于执行 CRUD 操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker, Session from app.core.config import settings
engine = create_engine(settings.DATABASE_URL, pool_pre_ping=True)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
def get_db() -> Session: """ 依赖注入工具函数: 每个请求创建一个独立的 Session,请求结束后自动关闭 """ db = SessionLocal() try: yield db finally: db.close()
|
在 backend/models 中定义表结构。所有模型继承自 SQLAlchemy 的 Base 类。
1 2 3 4 5 6 7 8 9 10
| from sqlalchemy import Column, Integer, String, Date, Float from app.db.base_class import Base
class Animal(Base): __tablename__ = "animals"
id = Column(Integer, primary_key=True, index=True) name = Column(String, nullable=False) acquire_date = Column(Date, nullable=False)
|
这一层 Schema 的核心职责:精确描述数据库表的结构(字段类型、主键、外键、索引),直接对应 SQL 语句。
第二套:API Schema (Pydantic Layer)
这是后端与外界交互的“关口”,在 FastAPI 中,我们使用 Pydantic 来定义这套 Schema。通常位于 backend/app/schemas。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| from pydantic import BaseModel, Field, ConfigDict from typing import Optional from datetime import date, datetime
class AnimalBase(BaseModel): name: str = Field(..., min_length=1, max_length=100, description="动物名称") quantity: int = Field(..., ge=0, description="数量") acquire_date: date = Field(..., description="购入/出生日期") notes: Optional[str] = Field(None, max_length=500, description="备注")
class AnimalCreate(AnimalBase): pass
class AnimalResponse(AnimalBase): id: int created_at: datetime updated_at: datetime
model_config = ConfigDict(from_attributes=True)
|
默认情况下,Pydantic 只能读取字典(如 data['id'])。开启 from_attributes=True 后 ,Pydantic 可以读取对象属性(如 data.id)。此时我们可以直接把 SQLAlchemy 返回的数据库对象扔给 Pydantic,它能自动提取数据。
在 API 中使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @router.post("/", response_model=AnimalResponse, status_code=201) def create_animal( animal_data: AnimalCreate, db: Session = Depends(get_db) ): db_animal = Animal(**animal_data.model_dump()) db.add(db_animal) db.commit() db.refresh(db_animal) return db_animal
|
我们在这里直接 return 了一个 SQLAlchemy 的表结构类,它自动转换成了 AnimalResponse,这是 FastAPI 的强大功能之一。虽然函数 return db_animal 返回的是一个 ORM 对象,但装饰器中的 response_model=AnimalResponse 会介入。FastAPI 会利用 Pydantic 的 from_attributes=True 特性,从 db_animal 中提取字段,过滤掉未在 AnimalResponse 中定义的字段,并将数据序列化为 JSON 返回给前端。
第三套:Frontend Schema (TypeScript Layer)
数据流出后端后,前端也需要一套标准来“接住”这些数据。在 TypeScript 项目中,我们在 src/types 中定义 Interface。
这一层定义应与后端的 Pydantic Schema 保持一一对应。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| export interface Animal { id: number name: string quantity: number acquire_date: string notes: string | null created_at: string updated_at: string }
export interface AnimalCreate { name: string quantity: number acquire_date: string notes?: string }
|
为什么需要 TypeScript 接口?因为在 JavaScript 中,写 user.nmae (拼写错误) 只有在运行时才会报错。而在 TypeScript 中,因为有了 Interface 充当“模具”,编辑器会在敲代码的那一刻就标红报错,极大地提高了开发效率和安全性。
前端通过 Axios 发送请求时,泛型(Generics)能发挥巨大作用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import { apiClient } from './client'
export const animalsApi = { create: async (data: AnimalCreate): Promise<Animal> => { const response = await apiClient.post<Animal>('/animals', data) return response.data },
getList: async (): Promise<Animal[]> => { const response = await apiClient.get<Animal[]>('/animals') return response.data } }
|
总结:三套 Schema 的协作流
让我们看一个完整的“创建动物”流程,数据是如何变形的:
- 前端 (TypeScript): 用户填写表单,数据符合
AnimalCreate 接口。前端发送 JSON。
- 后端入口 (Pydantic): FastAPI 接收 JSON,使用
AnimalCreate (Pydantic) 进行校验(比如数量不能小于0)。
- 后端处理 (ORM): 校验通过的数据被转换为
Animal (SQLAlchemy) 模型,写入数据库表。
- 后端出口 (Pydantic): 数据库返回的 ORM 对象,被
AnimalResponse (Pydantic) 过滤和序列化,变回 JSON。
- 前端接收 (TypeScript): 前端收到 JSON,将其识别为
Animal 接口类型,渲染到列表中。
这三套 Schema 分别守护了数据库的完整性、API 的安全性和前端的类型安全。在大型系统中,这种分层架构是保持代码清晰、可维护的基石。