FastAPI依赖注入 -- pd的FastAPI笔记
文章目录
-
- [FastAPI依赖注入 -- pd的FastAPI笔记](#FastAPI依赖注入 -- pd的FastAPI笔记)
- 依赖注入
- 领域驱动设计(DDD)
依赖注入
依赖注入(DI) 是一种设计模式,允许我们将"功能依赖"以声明式的方式注入到路由、中间件或其他组件中。FastAPI 内建了强大的依赖注入系统,它不仅支持函数依赖,还支持类依赖、嵌套依赖等高级用法。
FastAPI 依赖注入依赖注入主要有三个级别,区别在于作用域不同:
- 路 径(Path Operation) 级别:最常用,注入到
@app.get()、@router.post()等装饰器下面的函数中 - 路由(APIRouter) 级别- 共享依赖:将依赖注入到整个路由器下的所有路径操作
- 全局(FastAPI) 级别-全局依赖:将依赖注入到整个应用的所有路径操作
Depends() 基本语法
py
from fastapi import FastAPI, Depends
app = FastAPI()
def get_query_param(query_param: str = 'default_value'):
return query_param.upper()
# 在路径操作中注入依赖
@app.get('/items/')
async def read_items(query_param: str = Depends(get_query_param)):
return {'query_param': query_param}
def get_pagination_params(query: str = None,skip: int = 0, limit: int = 10):
return {'query':query, 'skip': skip, 'limit': limit}
@app.get('/items_paging/')
async def read_items(pagination: dict = Depends(get_pagination_params)):
return pagination
-
get_query_param()是一个依赖函数,它接收一个参数query_param,默认值为'default_value'。 -
在
read_items()路径操作中,我们使用Depends()函数将get_query_param()注入为参数。 -
FastAPI自动处理参数提取和验证
-
而在调用
read_items()时,接收的参数实际上取决于get_pagination_params()定义的参数。
路径级依赖注入
在 FastAPI 中,路径级依赖注入是最常用的方式,它允许我们在单个路由中注入依赖项。这些依赖项会在请求到达路由处理函数之前执行,并可以用于参数验证、权限检查、数据库连接管理等。
特点
- 作用范围:仅影响当前路径操作(单个 API 端点)。
- 执行顺序:依赖项在路由处理函数之前执行。
- 参数传递:依赖项可以访问请求参数(如 Query 、Path、Header等)
- 返回值:依赖项可以返回数据,供路由处理函数使用。
python
from fastapi import Depends, FastAPI, HTTPException, status
app = FastAPI()
# 依赖函数:检查用户权限
def check_permissions(token:str = Header(...)):
if token != "secret-token":
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Invalid authentication token",
)
return {'user': 'admin'}
# 路径级依赖注入
@app.get("/admin/", dependencies=[Depends(check_permissions)])
async def admin_dashboard():
return {"message": "Welcome to the admin dashboard!"}
check_permissions是一个依赖函数,它检查传入的tokenadmin_dashboard路由使用dependencies参数来指定依赖函数
路由级依赖注入
在 FastAPI 中,路由级依赖注入允许我们为整个 APIRouter 下的所有路径操作(API 端点)注入相同的依赖项。这种方式适用于多个路由共享相同逻辑的情况,例如:
- 身份验证(所有路由都需要检查 JWT Token)
- 数据库会话管理(所有路由都需要访问数据库)
- 日志记录(所有路由都需要记录请求信息)
特点
- 作用范围:影响 APIRouter 下的所有路由。
- 执行顺序:依赖项在每个路由处理函数之前执行。
- 代码复用:避免在每个路由重复编写相同逻辑。
- 可组合性:可以和路径级依赖一起使用(路径级依赖优先级更高)。
py
from fastapi import Depends, FastAPI, APIRouter, HTTPException, Header
app = FastAPI()
def verify_token(x_token: str = Header(...)):
if x_token != "fake-super-secret-token":
raise HTTPException(status_code=403, detail='Invalid X-Token provided'
# 列表里可以添加多个依赖
router = APIRouter(dependencies=[Depends(verify_token), ])
@router.get('/admin/dashboard')
async def admin_dashboard():
return {'message': 'welcome to the admin dashboard'}
app.include_router(router, perfix='/api')
全局级依赖注入
在 FastAPI 中,全局级依赖注入允许我们为整个应用的所有路由注入相同的依赖项。这种方式适用于所有 API 都需要执行的逻辑
- 作用范围:影响整个 FastAPI 应用的所有路由。
- 执行顺序:依赖项在所有路由处理函数之前执行。
- 适用于:跨路由的通用逻辑(如日志、认证、限流)。
- 优先级:低于路径级和路由级依赖(可以被覆盖)。
py
from fastapi import FastAPI, Depends
app = FastAPI(dependencies=[Depends(get_token_header), Depends(log_request)])
async def get_token_header(x_token: str = Header(...)):
...
async def log_request(request: Request):
...
@app.get("/items/")
async def read_items():
...
return {"item": ['item1', 'item2']}
嵌套依赖注入
在 FastAPI 中,嵌套依赖注入允许一个依赖项本身依赖于另一个依赖项,形成依赖链。这种方式适用于复杂业务逻辑的拆分和复用,
例如:
- 权限验证 + 用户信息获取(先验证 Token,再查询用户信息)
- 数据库连接 + 事务管理(先获取数据库连接,再开启事务)
- 参数校验 + 业务逻辑处理(先校验参数,再执行业务逻辑)
- 依赖链:一个依赖可以调用另一个依赖。
- 执行顺序:从最外层依赖开始,逐层向内执行。
- 代码复用:避免重复逻辑,提高可维护性。
- 支持异步:嵌套依赖可以是同步或异步函数。
python
from fastapi import Depends, FastAPI, HTTPException, Header
app = FastAPI()
# 验证Token
async def verify_token(x_token: str = Header(...)):
if x_token != "fake-super-secret-token":
raise HTTPException(status_code=403, detail='Invalid X-Token provided')
return x_token
# 获取用户信息 依赖于verify_token
async def get_current_user(x_token: str = Depends(verify_token)):
return {'username': 'admin', 'token': x_token}
# 使用嵌套依赖
@app.get('/users/')
async def get_user_info(current_user: dict = Depends(get_current_user)):
return current_user
verify_token验证请求头中的token。get_current_user依赖于verify_token,并返回用户信息。get_user_info使用get_current_user,形成依赖链:先执行verify_token再执行get_current_user→ 最后执行get_user_info。
依赖注入(类注入)
在 FastAPI 中,类依赖注入允许我们使用类(Class)作为依赖项,而不仅仅是函数。这种方式特别适合封装复杂逻辑或需要维护状态
的场景,例如:
- 数据库服务(封装 CRUD 操作)
- 外部 API 客户端(封装 HTTP 请求)
- 配置管理(读取和缓存配置)
特点
- 面向对象:可以封装属性和方法。
- 可复用性:类实例可以在多个路由中共享。
- 依赖嵌套:类可以依赖其他类或函数。
- 支持初始化参数:可以通过
__init__传递配置。
python
from fastapi import Depends, FastAPI
app = FastAPI()
class UserService:
def __init__(self, db_connection: str="default"):
self.db_connection = db_connection
def get_user(self, user_id: int):
return {'id': user_id, 'username': 'admin', 'db_connection': self.db_connection}
def get_user_service(db_connection: str = "test-db") -> UserService:
return UserService(db_connection)
@app.get('/users/{user_id}')
async def read_user(
user_id: int,
user_service: UserService = Depends(get_user_service)
):
return user_service.get_user(user_id)
if __name__ == '__main__':
import uvicorn
uvicorn.run(app, host='127.0.0.1', port=8000, reload=True)
领域驱动设计(DDD)
DDD(Domain-Driven Design)是一个让程序员和产品经理(或者任何提需求的人)不再"鸡同鸭讲"的超级沟通和做事的办法。
以后工作时,可能碰到的问题:
- 代码越写越乱,改一个功能要动十几个地方。
- 数据库表设计好了,但业务逻辑全散在 Service 里,没人看得懂。
- 产品经理说"会员下单有折扣",你却不知道该写在哪。
- 和产品沟通时总对不上口径:"你说的'下单成功'到底指啥?"
这些问题,DDD 就是来解决的
-
DDD 的核心思想
- 业务优先:先搞懂业务是怎么运作的,而不是一上来就建表。
- 统一语言:程序员、产品经理、业务方用同一个词描述同一个东西。
- 边界清晰:把大系统拆成几个"小王国",每个王国自己管自己
-
DDD 核心概念
- 领域:软件要解决的那个业务范围
- 通用语言:团队里所有人(程序员、产品经理、测试等)约定好的一套"黑话"。讨论业务、写代码、写文档都用这套话,不准出现第二种说法
- 限界上下文:给"通用语言"划边界。同一个词,在不同的部门(上下文)里,意思可能完全不同。限界上下文就是把这些部门隔开,让每个部门内部用自己的"方言"而不会混乱。
例子:"订单"这个词:
- 在 【用户下单上下文】 里,"订单"关心的是:菜品、价格、优惠券、送餐地址。
- 在 【后厨制作上下文】 里,"订单"关心的是:做什么菜、做菜顺序、出餐口。
- 在 【配送调度上下文】 里,"订单"关心的是:取餐地点、送餐地点、路径规划。
关键点:你不能把这三个上下文的"订单"混在一起设计成一个巨无霸
Order类,会复杂到爆炸。限界上下文就是告诉你,应该把它们当成三个不同的东西来开发,甚至可以做成三个不同的微服务。这是降低复杂度的超级大招。
- DDD 核心概念(续)
4. 实体:有唯一ID的东西,你看重的是"它是谁",而不是它"是什么样"。它会变化,但它的身份(ID)不变
5. 值对象:没ID,只看属性值的东西。你看重的是"它是什么样",如果两个东西的所有属性值一样,就可以认为它们是同一个东西
6. 聚合根:一组相关对象的"老大",外部只能通过它来访问这组对象,它是保证业务一致性的边界。
例子:用户是一个实体。因为每个用户都有一个唯一的用户ID(比如123)。用户"张三"今天可以改名叫"张四",密码也可以改,但他的用户ID 123没变,我们就知道他还是那个"张三"
例子:地址是一个典型的值对象。你有一个地址对象:=中国,城市=北京,街道=xxx路1号。如果另一个地址对象的这国家些属性值完全一样,那它们就是相等的,即使它们在内存里是两个不同的对象。值对象通常还是不可变的,你的地址信息一旦创建就不会轻易改动,要改就是整个换掉。
关键点:
- 聚合根是实体(有ID),但不是所有实体都是聚合根。
- 一个聚合包含多个实体和值对象,但外部只能通过聚合根操作它们(类似"黑盒")。
聚合根负责维护聚合内部的业务规则一致性。
为什么需要聚合根?
假设"订单"是一个聚合根,包含:
- 订单实体(根)
- 订单项(子实体)
- 配送地址(值对象)
没有聚合根时的混乱:
- 代码可能直接修改
OrderItem的价格,导致订单总价不一致。 - 可能误删
Address,导致订单数据不完整。
有聚合根后的规则:
- 要改订单项?必须通过
Order聚合根的方法(如Order.updateItem()),会自动重新计算总价。 - 要删地址?必须调用
Order.changeAddress(),确保新地址有效后才替换旧地址。
如何识别聚合根?
- 业务高频操作点:比如"下单"场景中,
Order是天然聚合根。 - 强一致性需求:比如"银行账户"转账,
Account必须是聚合根,保证余额变化原子性。 - 生命周期控制:比如"论坛帖子"删除时,连带回复一起删,
Post就是聚合根
领域服务: 处理那些不属于任何实体/值对象的业务逻辑,通常是跨聚合的、无状态的、需要协调多个领域对象的操作
假设你要实现"从账户A转账100元到账户B"。
这个操作涉及两个账户,你不能把 transferTo 方法放在 Account 类里,因为那会变成:
py
accountA.transferTo(accountB, 100);
解决方案:
python
TransferService.execute(from_account, to_account, amount)
DDD分层架构

- 用户接口层(User Interface Layer)
- 别名:表现层、Web层、接口层、Controller层
- 职责:
- 接收外部请求(如 HTTP、RPC、CLI)
- 解析输入参数(如 JSON、表单)
- 执行基础校验(如非空、格式)
- 调用应用层服务完成业务操作
- 返回响应结果(如 JSON、页面)
- 应用层(Application Layer)
- 别名:用例层、服务门面层
- 职责:
- 协调领域对象完成一个完整的业务用例(如"下单"、"转账")
- 处理事务边界(如开启事务)
- 发布领域事件
- 转换 DTO(数据传输对象)
- 不包含核心业务规则(只"指挥",不"决策")
- 依赖:
- 依赖 领域层(使用实体、聚合、领域服务)
- 依赖 基础设施层(获取 Repository 实现)
- 领域层(Domain Layer)
- 别名:模型层、核心层
- 职责:
- 包含系统的核心业务逻辑和规则
- 定义领域模型:
- 实体
- 值对象
- 聚合根
- 领域服务
- 领域事件
- 保证业务一致性(如订单金额不能为负)
- 不依赖任何外部框架(如 Flask、Django、FastAPI、SQLAlchemy)
- 关键原则:
- 业务逻辑必须在这里实现
- 聚合根负责维护内部一致性
- 基础设施层(Infrastructure Layer)
- 别名:数据访问层、技术实现层
- 职责:
- 提供技术实现,支撑上层运行
- 实现领域层定义的接口,例如:
- OrderRepository 接口的具体实现(如 JPA、MyBatis)
- 消息队列发送器(如 KafkaProducer)
- 外部 API 调用(如支付网关)
- 处理数据库、缓存、文件、邮件等底层操作
项目结构示意
my_fastapi_ddd_project/
│
├── app/ # 应用主目录
│ ├── __init__.py
│ │
│ ├── domain/ # 领域层(核心业务逻辑)
│ │ ├── __init__.py
│ │ ├── shared/ # 共享模块
│ │ ├── models/ # 领域实体(Entities)、值对象(Value Objects)
│ │ │ ├── __init__.py
│ │ │ ├── user.py # 例如 User 实体
│ │ │ └── ...
│ │ ├── repositories/ # 抽象的仓储接口(抽象类或 Protocol)
│ │ │ ├── __init__.py
│ │ │ └── user_repository.py # 定义 UserRepository 接口
│ │ ├── services/ # 领域服务(纯业务逻辑,无外部依赖)
│ │ │ ├── __init__.py
│ │ │ └── user_service.py
│ │ └── exceptions/ # 领域异常
│ │ ├── __init__.py
│ │ └── user_not_found.py
│ │
│ ├── application/ # 应用层(协调领域对象完成用例)
│ │ ├── __init__.py
│ │ ├── use_cases/ # 应用服务 / 用例(Use Cases)
│ │ │ ├── __init__.py
│ │ │ ├── create_user_use_case.py
│ │ │ └── get_user_use_case.py
│ │ └── dto/ # 数据传输对象(DTOs)
│ │ ├── __init__.py
│ │ ├── user_dto.py # 如 CreateUserRequest, UserResponse 等
│ │
│ ├── infrastructure/ # 基础设施层(实现细节:DB、第三方服务等)
│ │ ├── __init__.py
│ │ ├── database/ # 数据库相关(SQLAlchemy, asyncpg 等)
│ │ │ ├── __init__.py
│ │ │ ├── session.py # DB 会话管理
│ │ │ └── models/ # ORM 模型(与 domain.models 分离)
│ │ │ ├── __init__.py
│ │ │ └── user_model.py
│ │ ├── repositories/ # 仓储的具体实现
│ │ │ ├── __init__.py
│ │ │ └── user_repository_impl.py # 实现 domain 中定义的接口
│ │ └── external/ # 外部服务集成(如邮件、支付等)
│ │ ├── __init__.py
│ │ └── email_service.py
│ │
│ ├── presentation/ # 表示层(FastAPI 路由、请求处理)
│ │ ├── __init__.py
│ │ ├── api/ # API 路由
│ │ │ ├── __init__.py
│ │ │ ├── v1/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── users.py # 用户相关路由
│ │ │ │ └── ...
│ │ │ └── deps.py # 依赖注入(如获取 DB 会话、仓储实例)
│ │ └── schemas/ # Pydantic 模型(用于请求/响应验证)
│ │ ├── __init__.py
│ │ └── user_schema.py # CreateUserData, UserData 等
│ │
│ └── main.py # FastAPI 应用入口
│
├── tests/ # 测试目录(可按层划分)
│ ├── unit/
│ ├── integration/
│ └── e2e/
│
├── requirements.txt
├── alembic/ # 数据库迁移(如果使用 SQLAlchemy + Alembic)
└── README.md