App Components
What ships in every standard app, and how the pieces relate. This is the
blueprint's component model — templates/app/ is a runnable instance of it.
🗂️ Component model
The blueprint has two orthogonal views: runtime flow (who calls whom) and composition-root wiring (who constructs whom). Keeping them separate makes the layering visible instead of hiding it behind a hub.
🔀 Runtime flow
Who calls whom at request and job time:
%%{init: {"flowchart": {"nodeSpacing": 50, "rankSpacing": 80}}}%%
flowchart TB
subgraph REQ["Request path — API process"]
direction LR
CL(["Client<br/>operationId"]) --> RT["interfaces/<resource>/routes.py"]
RT --> SVC["<resource>_svc"]
SVC --> REPO["<resource>_repo"]
end
subgraph WRK["Background path — worker process"]
direction LR
W["worker.py"] --> JSVC["job_svc"]
W --> PIPE["pipelines/<family>"]
PIPE --> ISVC["item_svc"]
JSVC --> JREPO["job_repo"]
ISVC --> IREPO["item_repo"]
end
subgraph INFRA["infra/ — adapters"]
MONGO[("MongoDB / mongo_ops")]
end
REPO --> MONGO
JREPO --> MONGO
IREPO --> MONGO
Legend — solid arrows are runtime calls. Nothing is self-constructed.
🧵 Composition-root wiring
Who constructs what at startup:
%%{init: {"flowchart": {"nodeSpacing": 50, "rankSpacing": 80}}}%%
flowchart TB
INIT["init.py — composition root"]
SETUP["infra/db/setup.py"]
FACT["domain/*/ports/*<br/>factory seams"]
REPOS["repos<br/>infra/db/repos/mongo.py"]
SVCES["services<br/>CrudsServiceAdaptor"]
STATE["app.state.db_resources"]
HEXA["hexa.build"]
RT["routes.py"]
WK["worker.py"]
INIT --> SETUP
INIT --> FACT
FACT --> REPOS
FACT --> SVCES
REPOS --> SVCES
HEXA -.-> SVCES
INIT --> STATE
STATE --> RT
INIT --> WK
Legend —
init.pyis the only place that imports concrete adapters. Theports/factory seams bindinfra; the API reads resources fromapp.state.db_resources, the worker callsinit()directly.
📦 Component roles
1. interfaces/ — the edge
Thin handlers bound to openapi.yaml operationIds. They parse the request,
call a service, and shape the response. No business rules, no I/O.
interfaces/openapi.pyre-exports every route module so the OpenAPI-first app can resolve handlers.- Routes may mount the
cachedecorator on read endpoints.
2. domain/ — the core
Models, ports, and services (use cases). No framework imports. The ports/
package is the one seam allowed to reach into infra/: each port module declares
the document shape and a factory that binds the adapter.
<aggregate>/models.py— dataclasses, enums, document shapes.<aggregate>/ports/db.py— document model +create_<aggregate>_repo().<aggregate>/ports/service.py— service +get_<aggregate>_service(repo).- Services compose ports and enforce the business rules; the port factories bind the implementations, and the composition root calls them.
3. infra/ — the adapters
Concrete implementations of the domain ports plus cross-cutting infrastructure:
db/— mongo-ops repositories and the connection lifecycle.adapters/— cache store, progress bus, storages, fetchers (library-backed).- Anything else the domain needs, implemented against its ports.
4. pipelines/ — data-driven families (optional)
Extraction and enrichment families, each a deterministic step chain
(abc.py / impl.py / pipeline.yaml / resources/). Roots are registered
in the pattern registry and driven either by a worker job or a CLI.
5. worker.py — the job worker (optional)
A separate process that polls durable job collections, runs pipeline roots, and publishes progress to the progress bus. It shares the same composition root as the API.
6. init.py — the composition root
The only place anything is constructed: leaf repositories → dependent
repositories → services → adapters. Exposes get_svc overloads and starts /
stops resources on the app lifespan.
🔗 How the request flows
sequenceDiagram
participant C as Client
participant R as route (interfaces)
participant S as service (port factory / adapter)
participant P as repo (infra)
participant DB as MongoDB
C->>R: POST /items (operationId)
R->>S: svc.create(payload)
S->>P: repo.insert(doc)
P->>DB: insert
DB-->>P: ack
P-->>S: doc
S-->>R: result
R-->>C: 200 JSON