1. 从工具到架构:重新认识FastAPI Depends
如果你用过Spring,肯定对@Autowired不陌生,那种“我需要什么,容器就给我什么”的感觉,让代码变得干净又解耦。FastAPI的Depends机制,本质上也是依赖注入,但它更轻量、更Pythonic。很多朋友刚开始用FastAPI时,只是把Depends当成一个“获取当前用户”或者“获取数据库连接”的工具函数来用,这其实大大低估了它的威力。
在我经历过的几个从单体向微服务演进的项目里,早期大家往往只把Depends用在路由层的参数校验和简单依赖获取上。随着业务膨胀,服务层、仓储层、工具层的依赖关系越来越复杂,新同事接手代码时,常常要花半天时间才能理清一个接口背后到底初始化了多少个对象,资源泄露和循环依赖的问题也时不时冒出来。这时候,一个清晰的、基于Depends的依赖注入架构,就成了项目能否持续健康发展的关键。
简单来说,依赖注入的核心思想是“声明而非创建”。你的函数或类不需要关心依赖对象是如何被实例化的,它只需要声明“我需要什么”。FastAPI的Depends就是实现这一思想的利器。但把它从一个小工具升级为支撑整个应用的核心架构组件,需要我们转变视角:不再把它看作一个孤立的装饰器或参数,而是一个声明式依赖关系图的构建器。你的整个应用,从配置加载、数据库连接池、缓存客户端,到业务服务、仓储层、权限验证,都可以通过这张图清晰地组织起来,由FastAPI框架在请求生命周期中自动为你组装和清理。
这种架构带来的好处是实实在在的。首先,可测试性极大提升。你想测一个服务层的函数?直接传入mock的仓储对象就行,完全不用启动整个Web服务器或连接真实数据库。其次,代码可读性和可维护性直线上升。每个组件的依赖关系在函数签名里一目了然,新人也能快速理解数据流向。最后,资源管理变得异常简单。数据库连接、Redis连接、文件句柄这些需要手动关闭的资源,通过yield依赖可以确保在任何情况下(哪怕请求处理中途出错)都能被正确清理,再也不用写一堆try...finally了。
接下来的内容,我会带你一步步构建这样一个企业级的依赖注入架构。我们从最基础的用法开始,但重点会放在如何设计依赖链、如何管理生命周期、如何组织项目结构这些实战进阶技巧上。这些经验都是我在多个生产项目中踩过坑、重构过代码后总结出来的,希望能帮你少走弯路。
2. 依赖注入的核心模式与设计原则
2.1 依赖工厂:不仅仅是获取,更是配置
最基础的Depends用法是直接返回一个对象。但在企业级应用中,依赖的创建往往需要配置。这时,“依赖工厂”模式就派上用场了。它不是一个具体的类,而是一种设计思路:用一个函数来封装依赖对象的创建逻辑,这个函数本身也可以依赖其他配置。
举个例子,我们不再直接返回一个数据库连接,而是返回一个根据配置动态创建的数据库连接池。
from typing import Generator
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, Session
from app.core.config import settings # 假设你的配置从这里来
# 依赖工厂:创建数据库引擎(通常全局一个)
engine = create_engine(settings.DATABASE_URL, pool_pre_ping=True, pool_recycle=3600)
# 依赖工厂:创建会话工厂
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
def get_db() -> Generator[Session, None, None]:
"""
获取数据库会话的依赖工厂。
使用yield,确保请求结束后会话被正确关闭。
这是资源生命周期管理的经典模式。
"""
db = SessionLocal()
try:
yield db
# 如果请求处理成功,这里可以执行提交(但通常我们在服务层控制事务)
finally:
db.close() # 无论请求成功还是异常,都会执行关闭
这个get_db就是一个典型的依赖工厂。它不关心谁调用它,只负责按需生产一个数据库会话(Session)对象,并在使用完毕后清理。现在,任何需要数据库操作的路由或服务,只需要声明它依赖get_db。
但配置可能来自环境变量、配置文件或远程配置中心。我们可以再抽象一层:
from functools import lru_cache
from pydantic import BaseSettings
class Settings(BaseSettings):
database_url: str = "postgresql://user:pass@localhost/dbname"
redis_url: str = "redis://localhost:6379/0"
jwt_secret: str = "your-secret-key"
class Config:
env_file = ".env"
@lru_cache() # 使用缓存,避免每次请求都读取环境变量
def get_settings() -> Settings:
"""配置依赖工厂。使用缓存确保全局唯一配置实例。"""
return Settings()
# 数据库依赖现在可以依赖于配置
def get_db(settings: Settings = Depends(get_settings)) -> Generator[Session, None, None]:
engine = create_engine(settings.database_url)
SessionLocal = sessionmaker(bind=engine)
db = SessionLocal()
try:
yield db
finally:
db.close()
这里的设计原则是:高层依赖(如数据库)可以依赖于底层依赖(如配置)。FastAPI会自动解析这条依赖链。get_settings被lru_cache装饰,意味着在整个应用生命周期中它只会被执行一次,返回同一个Settings实例,这既高效又保证了配置的一致性。
2.2 依赖链与分层架构:清晰的边界
依赖注入最强大的地方在于可以形成依赖链(Dependency Chain),这天然契合分层架构。一个典型的三层架构(API层、服务层、仓储层)可以这样用依赖链串联起来。
我们先定义仓储层(Repository),它负责最底层的数据访问:
# app/repositories/user_repository.py
from sqlalchemy.orm import Session
from app.models.user import User
class UserRepository:
def __init__(self, db: Session):
self.db = db
def get_by_id(self, user_id: int) -> User | None:
return self.db.query(User).filter(User.id == user_id).first()
def get_by_email(self, email: str) -> User | None:
return self.db.query(User).filter(User.email == email).first()
def create(self, user_data: dict) -> User:
db_user = User(**user_data)
self.db.add(db_user)
self.db.commit()
self.db.refresh(db_user)
return db_user
# 仓储层的依赖工厂
def get_user_repository(db: Session = Depends(get_db)) -> UserRepository:
"""获取用户仓储实例,它依赖于数据库会话。"""
return UserRepository(db)
接下来是服务层(Service),它封装业务逻辑,并依赖于仓储层:
# app/services/user_service.py
from app.repositories.user_repository import UserRepository
from app.core.security import verify_password, create_access_token
from datetime import timedelta
class UserService:
def __init__(self, user_repo: UserRepository, jwt_secret: str, access_token_expire_minutes: int):
self.user_repo = user_repo
self.jwt_secret = jwt_secret
self.access_token_expire_minutes = access_token_expire_minutes
def authenticate_user(self, email: str, password: str) -> User | None:
user = self.user_repo.get_by_email(email)
if not user or not verify_password(password, user.hashed_password):
return None
return user
def create_access_token_for_user(self, user: User) -> str:
token_data = {"sub": str(user.id)}
expires_delta = timedelta(minutes=self.access_token_expire_minutes)
return create_access_token(token_data, self.jwt_secret, expires_delta)
# 服务层的依赖工厂
def get_user_service(
user_repo: UserRepository = Depends(get_user_repository),
settings: Settings = Depends(get_settings)
) -> UserService:
"""获取用户服务实例,它依赖于用户仓储和全局配置。"""
return UserService(user_repo, settings.jwt_secret, settings.access_token_expire_minutes)
最后是API层(路由),它处理HTTP请求,并依赖于服务层:
# app/api/v1/endpoints/auth.py
from fastapi import APIRouter, Depends, HTTPException, status
from app.schemas.token import Token
from app.schemas.user import UserLogin
from app.services.user_service import UserSe


2110

被折叠的 条评论
为什么被折叠?



