Skip to content

Application Builders & Dependency Injection (DI)

This document explains the builders pattern used by the application layer to compose infrastructure adapters and pass typed ports to the domain orchestrator.

  • Location: src/application/builders/
  • Purpose: Keep wiring explicit, testable, and out of domain logic.
  • Scope: Compose repositories/adapters using app config, and expose typed domain ports.

Core Principles

  • Clean Architecture: The application layer wires infrastructure and passes typed ports to the domain. The domain stays pure and unaware of infra details.
  • DI Over Patching: Accept overrides (clients, repos, factories, prebuilt ports) instead of relying on patching during tests.
  • Boundary-First Types: Domain ports use Pydantic models for IO types; adapters convert infra outputs to these models.
  • Simplicity: Single-user assumption; default wiring simple and predictable.

Builders

  • build_transactions_port(user_id, *, async_db=None, app_user_repo=None, turso_repo_factory=None)
  • Returns a TransactionsQueryPort or None if the user lacks Turso access.
  • DI hooks:

    • async_db: Firestore AsyncClient
    • app_user_repo: FirestoreAppUserRepository
    • turso_repo_factory: (user_id: str, access_token: str) -> TursoPlaidTransactionRepository
  • build_transaction_statistics_port(user_id, *, async_db=None, app_user_repo=None, turso_repo_factory=None)

  • Returns a TransactionStatisticsPort or None.
  • Same DI hooks as above.

Usage in Runtime

src/application/agent/runtime.py:handle_query composes ports using builders, unless provided via overrides:

text = await handle_query(
    user_id="u",
    query="...",
    now=now,
    tz=tz,
    output_format=fmt,
    transactions_port=maybe_fake_tx_port,
    stats_port=maybe_fake_stats_port,
    async_db=maybe_async_db,
    app_user_repo=maybe_app_user_repo,
    turso_repo_factory=maybe_factory,
)

In tests, pass fake ports to avoid wiring and patching.

DI Guard

scripts/guards/scan_file.py flags inline infra instantiations in /src/application/ (except /src/application/builders/) when they occur inside functions that do not start with build_, compose_, or wire_.

  • Banned suffixes: Repository, Client, Adapter, Service, Manager, Factory
  • Purpose: enforce DI-friendly composition at the application boundary.

Rationale

  • Improves testability: No need to patch module paths; inject fakes/stubs directly.
  • Separates concerns: Domain focuses on business logic; application handles composition.
  • Keeps imports light in business code; heavier infra types appear under TYPE_CHECKING in builders and runtime.