1. 为什么需要超越基础的依赖注入?
在FastAPI项目中,当系统复杂度达到一定规模时,简单的依赖注入用法就会暴露出诸多问题。我曾在开发一个电商平台的后端时,发现基础依赖注入方式导致代码出现以下典型症状:
- 路由文件臃肿不堪,单个文件超过2000行代码
- 业务逻辑与基础设施代码高度耦合
- 单元测试需要大量模拟(mock)操作
- 重复的依赖声明散布在各个路由中
这些问题本质上是因为基础依赖注入只解决了"如何获取依赖"的问题,而没有解决"如何组织依赖"这一更高层次的问题。让我们看一个典型的反模式示例:
python复制# 反模式:直接在路由中声明所有依赖
@app.post("/orders")
async def create_order(
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
redis: Redis = Depends(get_redis),
settings: Settings = Depends(get_settings)
):
# 业务逻辑与基础设施混杂
if not current_user.is_active:
raise HTTPException(...)
order = OrderCreate(...)
await redis.lock(f"order:{current_user.id}")
try:
db_order = create_order_in_db(db, order)
await redis.publish("order_created", db_order.id)
finally:
await redis.unlock(f"order:{current_user.id}")
return db_order
这种写法至少有三大问题:1) 业务逻辑与基础设施耦合;2) 依赖关系难以测试;3) 重复的依赖声明。接下来我们将探讨如何通过高级架构模式解决这些问题。
2. 分层依赖注入架构
2.1 领域层与基础设施分离
正确的做法是将系统划分为清晰的层次,每层有明确的职责边界。我推荐采用如下分层结构:
code复制src/
├── domain/ # 纯业务逻辑
├── infrastructure/ # 技术实现细节
├── application/ # 用例协调层
└── presentation/ # 接口层(FastAPI路由)
在这种架构下,依赖流向应该是单向的:presentation → application → domain ← infrastructure。领域层不依赖任何其他层,保持纯粹的业务逻辑。
2.2 依赖倒置实现
通过抽象基类(ABC)实现依赖倒置是核心技巧。例如数据库操作应该这样抽象:
python复制# domain/repositories.py
from abc import ABC, abstractmethod
class OrderRepository(ABC):
@abstractmethod
async def create(self, order: OrderCreate) -> Order:
pass
# infrastructure/repositories.py
from domain.repositories import OrderRepository
class SQLOrderRepository(OrderRepository):
def __init__(self, session: Session):
self.session = session
async def create(self, order: OrderCreate) -> Order:
db_order = Order(**order.dict())
self.session.add(db_order)
await self.session.commit()
return db_order
2.3 依赖组合根模式
组合根(Composition Root)是依赖注入的核心模式,建议在项目启动时集中配置所有依赖:
python复制# infrastructure/container.py
from dependency_injector import containers, providers
from sqlalchemy.ext.asyncio import create_async_engine
class Container(containers.DeclarativeContainer):
wiring_config = containers.WiringConfiguration(modules=[
"presentation.routers",
"application.services"
])
db = providers.Singleton(
create_async_engine,
settings.DATABASE_URL
)
session = providers.Factory(
sessionmaker,
bind=db,
class_=AsyncSession,
expire_on_commit=False
)
order_repository = providers.Factory(
SQLOrderRepository,
session=session
)
3. 高级依赖注入模式实战
3.1 基于装饰器的依赖组合
对于复杂的业务用例,可以使用装饰器组合多个依赖项:
python复制# application/dependencies.py
from functools import wraps
def with_transaction(fn):
@wraps(fn)
async def wrapper(
repo: OrderRepository = Depends(Container.order_repository),
session: AsyncSession = Depends(Container.session),
*args, **kwargs
):
async with session.begin():
try:
result = await fn(repo=repo, *args, **kwargs)
await session.commit()
return result
except Exception:
await session.rollback()
raise
return wrapper
# 使用示例
@with_transaction
async def create_order_use_case(
order_data: OrderCreate,
repo: OrderRepository
) -> Order:
return await repo.create(order_data)
3.2 动态依赖解析
某些场景需要根据运行时条件决定依赖的具体实现。可以通过依赖覆盖机制实现:
python复制def get_repository():
if settings.ENV == "test":
return Container.mock_order_repository
return Container.order_repository
@app.post("/orders")
async def create_order(
use_case: CreateOrderUseCase = Depends(get_repository)
):
...
3.3 依赖生命周期管理
FastAPI默认的依赖生命周期是每次请求都新建实例。对于重量级资源,应该自定义生命周期:
python复制# 单例生命周期示例
async def get_heavy_service():
if not hasattr(get_heavy_service, "instance"):
get_heavy_service.instance = await HeavyService.create()
return get_heavy_service.instance
4. 测试策略与依赖替换
4.1 单元测试依赖替换
使用依赖注入的最大优势是便于测试。这是我在实际项目中的测试配置方式:
python复制@pytest.fixture
def container():
container = Container()
container.db.override(create_async_engine("sqlite+aiosqlite:///:memory:"))
container.order_repository.override(MockOrderRepository())
yield container
container.unwire()
async def test_create_order(container: Container):
async with container.session() as session:
repo = container.order_repository(session=session)
order = await repo.create(OrderCreate(...))
assert order.id is not None
4.2 集成测试配置
对于集成测试,可以部分替换真实依赖:
python复制@pytest.fixture
def test_app(container):
app = FastAPI()
app.container = container
app.include_router(orders_router)
# 只替换外部服务依赖
container.email_service.override(MockEmailService())
container.wire(modules=["presentation.routers"])
yield app
container.unwire()
5. 性能优化技巧
5.1 依赖缓存机制
对于计算密集型依赖,可以使用lru_cache:
python复制from functools import lru_cache
@lru_cache
def get_expensive_service():
return ExpensiveService()
@app.get("/data")
async def get_data(
service: ExpensiveService = Depends(get_expensive_service)
):
...
5.2 异步依赖预加载
对于启动慢的依赖,可以在应用启动时预加载:
python复制@app.on_event("startup")
async def preload_dependencies():
app.state.cached_service = await HeavyService.create()
@app.get("/items")
async def read_items(
service: HeavyService = Depends(lambda: app.state.cached_service)
):
...
6. 复杂场景解决方案
6.1 跨微服务依赖
在微服务架构中,可以使用适配器模式封装外部服务调用:
python复制class ProductServiceAdapter:
def __init__(self, http_client: AsyncClient):
self.client = http_client
async def get_product(self, product_id: str) -> Product:
response = await self.client.get(
f"{settings.PRODUCT_SERVICE_URL}/products/{product_id}"
)
return Product(**response.json())
# 容器配置
container.product_service = providers.Factory(
ProductServiceAdapter,
http_client=Container.http_client
)
6.2 动态依赖配置
根据配置动态切换依赖实现:
python复制def get_payment_gateway():
if settings.PAYMENT_PROVIDER == "stripe":
return Container.stripe_gateway
elif settings.PAYMENT_PROVIDER == "paypal":
return Container.paypal_gateway
else:
raise ValueError("Invalid payment provider")
@app.post("/payments")
async def create_payment(
gateway: PaymentGateway = Depends(get_payment_gateway)
):
...
7. 架构演进建议
7.1 从单体到模块化
当项目规模扩大时,建议采用模块化架构:
code复制src/
├── modules/
│ ├── orders/
│ │ ├── domain/
│ │ ├── infrastructure/
│ │ └── presentation/
│ └── payments/
│ ├── domain/
│ ├── infrastructure/
│ └── presentation/
└── shared/
├── container.py
└── dependencies.py
每个模块有自己的子容器,主容器负责组合各模块:
python复制# shared/container.py
class Container(containers.DeclarativeContainer):
orders = providers.Container(OrdersContainer)
payments = providers.Container(PaymentsContainer)
7.2 依赖可视化工具
对于大型项目,可以使用pydeps等工具生成依赖关系图:
bash复制# 生成依赖图
pydeps src --cluster --rankdir=TB --output-file=dependencies.png
这个技巧在我负责的一个包含50+模块的项目中,帮助团队理清了复杂的依赖关系。
