Documentation

Dependency Injection

FastAPI's Depends() system, the DI container in api/dependencies.py, and infrastructure factory pattern.

Show:

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.py is 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.py receive Settings — they never call get_settings() themselves.
  • Handler constructors receive injected interfaces, not concrete implementations — keeping application layer infrastructure-agnostic.