写点什么

FastAPI 依赖管理的三种方式对比:依赖注入 vs LRU 缓存 vs 全局变量

作者:大法师
  • 2024-12-06
    北京
  • 本文字数:2209 字

    阅读完需:约 7 分钟

在 FastAPI 项目开发中,我们经常需要管理一些共享资源,比如数据库连接、日志记录器、API 客户端等。本文将详细对比三种常见的依赖管理方式:依赖注入(Dependency Injection)、全局方法+LRU 缓存、以及全局变量,分析它们的优缺点和适用场景。

1. 三种方式的基本用法

1.1 依赖注入 (Dependency Injection)

from fastapi import Dependsfrom logging import Logger
def get_logger() -> Logger: logger = logging.getLogger("my_app") return logger
@app.get("/api/items")async def get_items(logger: Logger = Depends(get_logger)): logger.info("Fetching items") return {"items": []}
复制代码

1.2 全局方法 + LRU 缓存

from functools import lru_cache
@lru_cache()def get_logger() -> Logger: logger = logging.getLogger("my_app") return logger
@app.get("/api/items")async def get_items(): logger = get_logger() logger.info("Fetching items") return {"items": []}
复制代码

1.3 全局变量

logger = logging.getLogger("my_app")
@app.get("/api/items")async def get_items(): logger.info("Fetching items") return {"items": []}
复制代码

2. 三种方式的对比

让我们从多个维度来对比这三种方式:

2.1 初始化时机

sequenceDiagram    participant App as Application    participant Module as Module    participant Resource as Resource
Note over App,Resource: 全局变量 App->>Module: Import module Module->>Resource: Initialize immediately Note over App,Resource: LRU Cache App->>Module: Import module App->>Resource: Initialize on first use Note over App,Resource: Dependency Injection App->>Module: Import module App->>Resource: Initialize per request/usage
复制代码


  • 全局变量:模块导入时立即初始化

  • LRU 缓存:第一次调用时初始化(懒加载)

  • 依赖注入:每次注入时初始化(可配置缓存)

2.2 资源管理示例

以数据库连接为例:


# 1. 依赖注入from fastapi import Dependsfrom sqlalchemy import create_enginefrom sqlalchemy.orm import Session
def get_db(): engine = create_engine("postgresql://user:pass@localhost/db") db = Session(engine) try: yield db finally: db.close()
@app.get("/users")def get_users(db: Session = Depends(get_db)): return db.query(User).all()
# 2. LRU缓存@lru_cache()def get_db_engine(): return create_engine("postgresql://user:pass@localhost/db")
def get_users(): engine = get_db_engine() with Session(engine) as db: return db.query(User).all()
# 3. 全局变量engine = create_engine("postgresql://user:pass@localhost/db")
def get_users(): with Session(engine) as db: return db.query(User).all()
复制代码

3. 各方式的优缺点

3.1 依赖注入

优点:


  • 依赖关系明确

  • 易于测试和 mock

  • 可以管理资源生命周期

  • 支持异步依赖

  • 适合请求级别的隔离


缺点:


  • 代码略显冗长

  • 有一定的性能开销

  • 配置相对复杂

3.2 LRU 缓存 + 全局方法

优点:


  • 延迟初始化

  • 自动缓存结果

  • 线程安全

  • 可控制缓存大小

  • 易于清除缓存(用于测试)


缺点:


  • 不能自动管理资源生命周期

  • 缓存可能占用内存

  • 不适合请求级别的隔离

3.3 全局变量

优点:


  • 代码最简单

  • 使用方便

  • 性能开销最小


缺点:


  • 难以测试和 mock

  • 可能造成循环导入

  • 初始化失败影响整个模块

  • 资源生命周期难以控制

4. 使用建议

4.1 使用依赖注入的场景

  • 需要请求级别隔离的资源

  • 需要管理资源生命周期

  • 需要频繁进行单元测试

  • 需要异步依赖

  • 配置可能经常变化


示例:数据库会话、请求专用的 API 客户端

4.2 使用 LRU 缓存的场景

  • 创建成本高的资源

  • 需要延迟初始化

  • 线程安全要求高

  • 需要缓存控制

  • 无状态或状态可共享的资源


示例:数据库连接池、全局配置、日志记录器

4.3 使用全局变量的场景

  • 常量配置

  • 简单的初始化操作

  • 确定不会失败的资源

  • 真正需要在启动时就初始化的资源


示例:应用常量、简单配置对象

5. 实际应用示例

一个结合三种方式的最佳实践:


from functools import lru_cachefrom fastapi import FastAPI, Dependsfrom sqlalchemy import create_enginefrom sqlalchemy.orm import Session
# 1. 全局常量 - 适合配置APP_NAME = "my_app"MAX_CONNECTIONS = 100
# 2. LRU缓存 - 适合共享资源@lru_cache()def get_db_engine(): return create_engine( "postgresql://user:pass@localhost/db", pool_size=MAX_CONNECTIONS )
@lru_cache()def get_logger(): return logging.getLogger(APP_NAME)
# 3. 依赖注入 - 适合请求级别的资源def get_db(): engine = get_db_engine() db = Session(engine) try: yield db finally: db.close()
app = FastAPI()
@app.get("/users")def get_users(db: Session = Depends(get_db)): logger = get_logger() logger.info("Fetching users") return db.query(User).all()
复制代码

6. 总结

选择合适的依赖管理方式应该基于以下因素:


  • 资源的生命周期要求

  • 初始化成本

  • 测试需求

  • 并发访问情况

  • 内存使用限制

  • 代码可维护性


在实际项目中,这三种方式往往是相互补充的,而不是互斥的。选择合适的方式可以让代码更加清晰、可维护,同时保持良好的性能。

用户头像

大法师

关注

还未添加个人签名 2018-02-21 加入

还未添加个人简介

评论

发布
暂无评论
FastAPI 依赖管理的三种方式对比:依赖注入 vs LRU缓存 vs 全局变量_FastApi_大法师_InfoQ写作社区