FastAPI依赖注入

FastAPI依赖注入 -- pd的FastAPI笔记

文章目录

依赖注入

依赖注入(DI) 是一种设计模式,允许我们将"功能依赖"以声明式的方式注入到路由、中间件或其他组件中。FastAPI 内建了强大的依赖注入系统,它不仅支持函数依赖,还支持类依赖、嵌套依赖等高级用法。

FastAPI 依赖注入依赖注入主要有三个级别,区别在于作用域不同:

  1. 路 径(Path Operation) 级别:最常用,注入到 @app.get()@router.post() 等装饰器下面的函数中
  2. 路由(APIRouter) 级别- 共享依赖:将依赖注入到整个路由器下的所有路径操作
  3. 全局(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 中,路径级依赖注入是最常用的方式,它允许我们在单个路由中注入依赖项。这些依赖项会在请求到达路由处理函数之前执行,并可以用于参数验证、权限检查、数据库连接管理等。

特点

  1. 作用范围:仅影响当前路径操作(单个 API 端点)。
  2. 执行顺序:依赖项在路由处理函数之前执行。
  3. 参数传递:依赖项可以访问请求参数(如 Query 、Path、Header等)
  4. 返回值:依赖项可以返回数据,供路由处理函数使用。
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 是一个依赖函数,它检查传入的 token
  • admin_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 都需要执行的逻辑

  1. 作用范围:影响整个 FastAPI 应用的所有路由。
  2. 执行顺序:依赖项在所有路由处理函数之前执行。
  3. 适用于:跨路由的通用逻辑(如日志、认证、限流)。
  4. 优先级:低于路径级和路由级依赖(可以被覆盖)。
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,再查询用户信息)
  • 数据库连接 + 事务管理(先获取数据库连接,再开启事务)
  • 参数校验 + 业务逻辑处理(先校验参数,再执行业务逻辑)
  1. 依赖链:一个依赖可以调用另一个依赖。
  2. 执行顺序:从最外层依赖开始,逐层向内执行。
  3. 代码复用:避免重复逻辑,提高可维护性。
  4. 支持异步:嵌套依赖可以是同步或异步函数。
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
  1. verify_token 验证请求头中的 token
  2. get_current_user 依赖于 verify_token ,并返回用户信息。
  3. 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 就是来解决的

  1. DDD 的核心思想

    • 业务优先:先搞懂业务是怎么运作的,而不是一上来就建表。
    • 统一语言:程序员、产品经理、业务方用同一个词描述同一个东西。
    • 边界清晰:把大系统拆成几个"小王国",每个王国自己管自己
  2. DDD 核心概念

    1. 领域:软件要解决的那个业务范围
    2. 通用语言:团队里所有人(程序员、产品经理、测试等)约定好的一套"黑话"。讨论业务、写代码、写文档都用这套话,不准出现第二种说法
    3. 限界上下文:给"通用语言"划边界。同一个词,在不同的部门(上下文)里,意思可能完全不同。限界上下文就是把这些部门隔开,让每个部门内部用自己的"方言"而不会混乱。

例子:"订单"这个词:

  • 在 【用户下单上下文】 里,"订单"关心的是:菜品、价格、优惠券、送餐地址。
  • 在 【后厨制作上下文】 里,"订单"关心的是:做什么菜、做菜顺序、出餐口。
  • 在 【配送调度上下文】 里,"订单"关心的是:取餐地点、送餐地点、路径规划。

关键点:你不能把这三个上下文的"订单"混在一起设计成一个巨无霸Order 类,会复杂到爆炸。限界上下文就是告诉你,应该把它们当成三个不同的东西来开发,甚至可以做成三个不同的微服务。这是降低复杂度的超级大招。

  1. DDD 核心概念(续)
    4. 实体:有唯一ID的东西,你看重的是"它是谁",而不是它"是什么样"。它会变化,但它的身份(ID)不变
    5. 值对象:没ID,只看属性值的东西。你看重的是"它是什么样",如果两个东西的所有属性值一样,就可以认为它们是同一个东西
    6. 聚合根:一组相关对象的"老大",外部只能通过它来访问这组对象,它是保证业务一致性的边界。

例子:用户是一个实体。因为每个用户都有一个唯一的用户ID(比如123)。用户"张三"今天可以改名叫"张四",密码也可以改,但他的用户ID 123没变,我们就知道他还是那个"张三"
例子:地址是一个典型的值对象。你有一个地址对象:=中国,城市=北京,街道=xxx路1号。如果另一个地址对象的这国家些属性值完全一样,那它们就是相等的,即使它们在内存里是两个不同的对象。值对象通常还是不可变的,你的地址信息一旦创建就不会轻易改动,要改就是整个换掉。
关键点:

  • 聚合根是实体(有ID),但不是所有实体都是聚合根。
  • 一个聚合包含多个实体和值对象,但外部只能通过聚合根操作它们(类似"黑盒")。
    聚合根负责维护聚合内部的业务规则一致性。

为什么需要聚合根?

假设"订单"是一个聚合根,包含:

  • 订单实体(根)
  • 订单项(子实体)
  • 配送地址(值对象)

没有聚合根时的混乱:

  • 代码可能直接修改OrderItem 的价格,导致订单总价不一致。
  • 可能误删Address ,导致订单数据不完整。

有聚合根后的规则:

  • 要改订单项?必须通过Order 聚合根的方法(如Order.updateItem() ),会自动重新计算总价。
  • 要删地址?必须调用Order.changeAddress() ,确保新地址有效后才替换旧地址。

如何识别聚合根?

  1. 业务高频操作点:比如"下单"场景中,Order 是天然聚合根。
  2. 强一致性需求:比如"银行账户"转账,Account 必须是聚合根,保证余额变化原子性。
  3. 生命周期控制:比如"论坛帖子"删除时,连带回复一起删,Post 就是聚合根

领域服务: 处理那些不属于任何实体/值对象的业务逻辑,通常是跨聚合的、无状态的、需要协调多个领域对象的操作

假设你要实现"从账户A转账100元到账户B"。

这个操作涉及两个账户,你不能把 transferTo 方法放在 Account 类里,因为那会变成:

py 复制代码
accountA.transferTo(accountB, 100); 

解决方案:

python 复制代码
TransferService.execute(from_account, to_account, amount)

DDD分层架构

  1. 用户接口层(User Interface Layer)
    • 别名:表现层、Web层、接口层、Controller层
    • 职责:
      • 接收外部请求(如 HTTP、RPC、CLI)
      • 解析输入参数(如 JSON、表单)
      • 执行基础校验(如非空、格式)
      • 调用应用层服务完成业务操作
      • 返回响应结果(如 JSON、页面)
  2. 应用层(Application Layer)
    • 别名:用例层、服务门面层
    • 职责:
      • 协调领域对象完成一个完整的业务用例(如"下单"、"转账")
      • 处理事务边界(如开启事务)
      • 发布领域事件
      • 转换 DTO(数据传输对象)
      • 不包含核心业务规则(只"指挥",不"决策")
    • 依赖:
      • 依赖 领域层(使用实体、聚合、领域服务)
      • 依赖 基础设施层(获取 Repository 实现)
  3. 领域层(Domain Layer)
    • 别名:模型层、核心层
    • 职责:
      • 包含系统的核心业务逻辑和规则
      • 定义领域模型:
        • 实体
        • 值对象
        • 聚合根
        • 领域服务
        • 领域事件
      • 保证业务一致性(如订单金额不能为负)
      • 不依赖任何外部框架(如 Flask、Django、FastAPI、SQLAlchemy)
    • 关键原则:
      • 业务逻辑必须在这里实现
      • 聚合根负责维护内部一致性
  4. 基础设施层(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
相关推荐
YJlio10 小时前
1.7 通过 Sysinternals Live 在线运行工具:不下载也能用的“云端工具箱”
c语言·网络·python·数码相机·ios·django·iphone
l1t11 小时前
在wsl的python 3.14.3容器中使用databend包
开发语言·数据库·python·databend
山塘小鱼儿12 小时前
本地Ollama+Agent+LangGraph+LangSmith运行
python·langchain·ollama·langgraph·langsimth
码说AI12 小时前
python快速绘制走势图对比曲线
开发语言·python
wait_luky12 小时前
python作业3
开发语言·python
Python大数据分析@14 小时前
tkinter可以做出多复杂的界面?
python·microsoft
大黄说说14 小时前
新手选语言不再纠结:Java、Python、Go、JavaScript 四大热门语言全景对比与学习路线建议
java·python·golang
小小张说故事14 小时前
SQLAlchemy 技术入门指南
后端·python
我是章汕呐14 小时前
拆解Libvio.link爬虫:从动态页面到反爬对抗的实战解析
爬虫·python