在 FastAPI 项目开发中,我们经常需要管理一些共享资源,比如数据库连接、日志记录器、API 客户端等。本文将详细对比三种常见的依赖管理方式:依赖注入(Dependency Injection)、全局方法+LRU缓存、以及全局变量,分析它们的优缺点和适用场景。
1. 三种方式的基本用法
1.1 依赖注入 (Dependency Injection)
from fastapi import Depends
from 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 Depends
from sqlalchemy import create_engine
from 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_cache
from fastapi import FastAPI, Depends
from sqlalchemy import create_engine
from 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. 总结
选择合适的依赖管理方式应该基于以下因素:
- 资源的生命周期要求
- 初始化成本
- 测试需求
- 并发访问情况
- 内存使用限制
- 代码可维护性
在实际项目中,这三种方式往往是相互补充的,而不是互斥的。选择合适的方式可以让代码更加清晰、可维护,同时保持良好的性能。