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
TransactionsQueryPortorNoneif the user lacks Turso access. -
DI hooks:
async_db: FirestoreAsyncClientapp_user_repo:FirestoreAppUserRepositoryturso_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
TransactionStatisticsPortorNone. - 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_CHECKINGin builders and runtime.