Dependency Injection
FastAPI's Depends() system, the DI container in api/dependencies.py, and infrastructure factory pattern.
Framework implementations
FastAPI's Depends() is the DI mechanism. All service construction and wiring lives in api/dependencies.py. Routes declare what they need via typed function parameters.
The DI Container: api/dependencies.py
This file is the single place where infrastructure is constructed and wired to application handlers. It has two kinds of factories:
Singleton factories — built once per process, cached via @lru_cache:
from functools import lru_cache
from config.settings import get_settings
from infrastructure.database.factories import build_session_factory
from infrastructure.events.factories import build_event_publisher
from infrastructure.monitoring.factories import build_performance_monitor
@lru_cache(maxsize=1)
def get_session_factory():
return build_session_factory(get_settings())
@lru_cache(maxsize=1)
def get_event_publisher():
return build_event_publisher(get_settings())
@lru_cache(maxsize=1)
def get_performance_monitor():
return build_performance_monitor(get_settings())Request-scoped factories — created per request, depend on singletons:
from application.account.create import CreateAccountHandler
from infrastructure.database.unit_of_work import build_unit_of_work
def get_account_repository(
session_factory=Depends(get_session_factory),
monitor=Depends(get_performance_monitor),
):
return build_account_repository(session_factory, monitor)
def get_create_account_handler(
account_repo=Depends(get_account_repository),
event_publisher=Depends(get_event_publisher),
unit_of_work=Depends(get_unit_of_work),
) -> CreateAccountHandler:
return CreateAccountHandler(
account_repository=account_repo,
event_publisher=event_publisher,
unit_of_work=unit_of_work,
)Usage in Routes
Routes declare dependencies via typed parameters — FastAPI resolves and injects them:
# api/routes/accounts.py
from fastapi import APIRouter, Depends
from api.dependencies import get_create_account_handler
from api.schemas.account_schemas import CreateAccountRequest, AccountResponse
from application.account.create import CreateAccountHandler
router = APIRouter(prefix="/accounts", tags=["accounts"])
@router.post("/", response_model=AccountResponse, status_code=201)
async def create_account(
body: CreateAccountRequest,
handler: CreateAccountHandler = Depends(get_create_account_handler),
):
result = await handler.execute(body.to_command())
return AccountResponse.from_domain(result)Auth Dependencies
Auth-related dependencies live in api/security.py and are composed into routes that require authentication:
# api/security.py
from fastapi import Depends, Request, HTTPException
from application.security.authorization import ActorContext
def get_actor_context(request: Request) -> ActorContext:
actor = getattr(request.state, "ctx", {}).get("actor")
if not actor:
raise HTTPException(status_code=401, detail="Authentication required")
return ActorContext(**actor)
def require_admin(actor: ActorContext = Depends(get_actor_context)) -> ActorContext:
if "admin" not in actor.roles:
raise HTTPException(status_code=403, detail="Admin role required")
return actor# In routes:
@router.get("/admin/accounts")
async def list_all_accounts(
actor: ActorContext = Depends(require_admin),
handler=Depends(get_list_accounts_handler),
):
...Infrastructure Factory Pattern
Each infrastructure factory follows the same shape:
# infrastructure/events/factories.py
from config.settings import Settings
from infrastructure.events.adapters.inmemory import InMemoryEventPublisher
from infrastructure.events.adapters.sns import SNSEventPublisher
ADAPTER_REGISTRY = {
"inmemory": _build_inmemory,
"sns": _build_sns,
}
def build_event_publisher(settings: Settings):
adapter = settings.event_adapter
builder = ADAPTER_REGISTRY.get(adapter)
if not builder:
raise ValueError(f"Unknown event adapter: {adapter}")
return builder(settings)
def _build_inmemory(settings: Settings) -> InMemoryEventPublisher:
return InMemoryEventPublisher()
def _build_sns(settings: Settings) -> SNSEventPublisher:
return SNSEventPublisher(
topic_arn=settings.event_sns_topic_arn,
region=settings.aws_region,
timeout=settings.event_publish_timeout,
)Rules
api/dependencies.pyis the only place that constructs infrastructure adapters. Routes never instantiate adapters directly.- Singleton services (stateless, expensive to construct) use
@lru_cache(maxsize=1). - Request-scoped services (stateful, per-request lifecycle) use
Depends()without caching. - Factories in
infrastructure/*/factories.pyreceiveSettings— they never callget_settings()themselves. - Handler constructors receive injected interfaces, not concrete implementations — keeping application layer infrastructure-agnostic.