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

339 阅读3分钟

在 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. 总结

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

  • 资源的生命周期要求
  • 初始化成本
  • 测试需求
  • 并发访问情况
  • 内存使用限制
  • 代码可维护性

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