Skip to content

Infra Adapters

Summary

All external systems are reached through adapters in infra/. Prefer an existing internal library over writing a new adapter; where no library exists, copy the Tier B scaffold from templates/app. See the Genericity Matrix for the Tier A / B / C split.


Database (MongoDB)

  • A single connection manager owns the client lifecycle. setup_database(uri, db_name) / shutdown_database().
  • A repository factory selects the repository flavour:
repo_factory(collection, document, engine=None,
             populate_rules=None, cache=None)
  • base repository — plain CRUD;
  • populating repository — resolves referenced documents (foreign keys);
  • cached repository — an LRU/TTL cache in front of reads.

  • Referenced documents are described declaratively:

PopulateRule(field_name="owner", collection_name="users",
             nested_rules=[], max_depth=2, projection=None)
  • Transactions use a session context manager provided by the library.

Tier A — Library (Mongo Ops). Do not write raw Motor calls in the domain; consult the registry and the library's MCP before touching the driver.

HTTP response cache

Tier B — template. Copy from templates/app; not a standalone library.

A generic GET cache sits behind a decorator.

@cache_response(ttl=60)
async def list_items(request): ...
  • Keyed on path + query + user.
  • No-op when no store is wired.
  • invalidate(*resources) bumps a generation counter for write-triggered invalidation.

Event / progress bus

Tier B — template. Copy from templates/app.

A port with an in-memory default adapter (publish, get_events). Swap for a durable broker in production (see Worker & Progress).

External clients (ingestion)

External fetchers (files, mail, HTTP) are adapters that wrap ingestion libraries. Construct them in the composition root and inject into orchestrators.

Tier A — Libraries: Omniread (formats) and Mail Intake (email).

LLM clients

Tier B — template. Copy the pool scaffold from templates/app.

When a pipeline calls a model provider, route through a pool adaptor that handles provider selection, retries, and client lifecycle:

pool = setup_async_pool(provider=settings.llm_provider)
result = await pool.complete(model="...", messages=[...])

Providers are selected by LLM_PROVIDER and configured by environment.

Key-value store

Tier B — template. Copy from templates/app.

For report/snapshot storage, provide a pluggable store port with a Redis adapter and an in-memory fallback.

Rule

  • Tier A (libraries: DB, DI, ingestion, mail, auth, docs, pipelines) — use the library; writing a private copy is a defect. Query find_library first.
  • Tier B (cache, progress bus, LLM pool, KV store, composition shape, worker, tests) — these are template code, not libraries: copy them from templates/app rather than searching the registry.
  • Tier C (models, business logic, pipeline families, rules) — author them yourself.