2.5 KiB
2.5 KiB
Composition Root
Summary
All wiring happens in exactly one module. It builds a flat resource registry and exposes it to the HTTP layer and to scripts.
The resource registry
init.py returns a plain dict[str, Any]. Keys follow two conventions:
<resource>_repo— a repository adapter for a resource.<resource>_svc— a service for a resource.
Plus named infrastructure adaptors:
http_cache_store— response cache backend.progress_bus— progress/event bus.<vendor>_<kind>— fetchers, clients, stores.
The API stores the dict on app.state.db_resources during lifespan startup.
Handlers read from it; nothing imports concrete adapters.
Shape
_resources: dict = {}
_initialized = False
async def init(settings=None) -> dict:
global _initialized
if _initialized:
return _resources
await setup_database(uri=settings.mongo_uri, db_name=settings.mongo_db)
# 1. leaf repositories
user_repo = create_user_repo()
# 2. dependent repositories (dependencies passed by keyword)
wallet_repo = create_wallet_repo(user_repo=user_repo)
# 3. services (build via the DI library, injecting the repo)
_resources["user_repo"] = user_repo
_resources["wallet_repo"] = wallet_repo
_resources["user_svc"] = get_user_service(user_repo)
_resources["wallet_svc"] = get_wallet_service(wallet_repo)
# 4. infrastructure adaptors (optional)
_resources["http_cache_store"] = HttpCacheStore(...)
_resources["progress_bus"] = ProgressEventBus()
_initialized = True
return _resources
async def shutdown() -> None:
global _initialized
await close_stores(_resources)
await shutdown_database()
_resources.clear()
_initialized = False
Typed accessors
Expose a get_svc(name) helper with one @overload per key so scripts get
typing without importing concrete classes:
@overload
def get_svc(name: Literal["user_svc"]) -> UserService: ...
@overload
def get_svc(name: Literal["wallet_svc"]) -> WalletService: ...
def get_svc(name: str):
return _resources[name]
Also expose get_db() (raw database handle) and pure helpers such as
compute_fingerprint(payload) (canonical hash used for deduplication).
Rules
init()is idempotent.- The composition root is the only place that imports concrete adapters.
- Handlers never call
init(); they readrequest.app.state.db_resources. - Scripts call
init()/shutdown()around their work.