1. 问题:路由函数越来越胖
典型坏味道:
1 |
|
依赖注入(Dependency Injection,DI)要回答:谁创建连接、谁校验身份、谁查库——路由只编排。
段末注释:DI 指由框架在调用路由前,按声明自动解析并注入依赖对象,而不是在函数内部
new或全局单例硬取。
2. 费曼白话:前台、后勤、柜员
延续 01 的比喻:
- 柜员 = 路由函数(接待这一单客户)。
- 后勤 =
Depends(...)提供的对象(数据库会话、当前用户、配置)。 - 前台系统 = FastAPI 依赖图解析器:先算后勤,再叫柜员;后勤之间也可互相依赖。
双重编码(结构图):
1 | 请求 → 中间件(04) |
3. Depends 组块
G1 简单依赖
1 | from fastapi import Depends, FastAPI |
G2 可复用依赖(yield 清理)
1 | from collections.abc import Generator |
- 适用:每请求一个 DB 会话,结束必关。
- 误用:在
yield前做重 IO 却不缓存——应放 lifespan(01)。
G3 依赖链
1 | def get_settings(): |
解析顺序:settings → db → user(框架按图拓扑排序)。
G4 Annotated 别名(推荐)
1 | from typing import Annotated |
路由签名更短,测试时 app.dependency_overrides 更好换假实现。
G5 路由级依赖
1 | router = APIRouter(dependencies=[Depends(verify_api_key)]) |
整组路由共享同一依赖,不必每个函数重复写。
G6 类依赖
同时需要注意,DEPEND不只是可以使用函数,也可以使用类,例如:
1 | fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}] |
4. 分层组块(工程目录)
小中型 API 推荐四层,依赖方向单向向下:
1 | app/ |
| 层 | 允许 | 禁止 |
|---|---|---|
| router | 调 service、抛 HTTPException | 直接写 SQL |
| service | 调 repo、业务校验 | 依赖 Request 对象 |
| repository | CRUD、查询 | 决定 HTTP 状态码 |
| schemas | 数据结构 | 业务逻辑 |
最小贯通示例:
1 | # services/items.py |
5. 测试挂钩:dependency_overrides
1 | def fake_db(): |
这是 DI 的工程收益:不换路由代码,换实现(07 展开)。
6. 合书自测
- 用一句话向同事解释
Depends在请求里的执行时机。 yield依赖和 lifespan 各解决什么?各跑几次?- 画 router → service → repo 三层,标一条「读 item」调用链。
7. 闪卡候选
| 正面 | 背面 |
|---|---|
Depends 解析顺序? |
按依赖图,被依赖者先执行 |
dependency_overrides 用途? |
测试/本地替换依赖实现 |
| router 能否 import repository? | 能但不推荐;经 service 保持业务集中 |
APIRouter(dependencies=[...])? |
路由组级共享依赖 |
小结
- DI 把横切能力(DB、用户、配置)从路由里拔出,FastAPI 用
Depends自动接线。 - 分层 把 HTTP 与业务、持久化分开,后续鉴权(06)、测试(07)、MCP 实战(08)都受益。
- 下一篇 04 中间件、异常与日志:全局横切与排错。