Domain isolation: typed contracts, service registry, and composable wiring
Add typed service contracts (Protocols + frozen DTOs) in shared/contracts/ for cross-domain communication. Each domain exposes a service interface (BlogService, CalendarService, MarketService, CartService) backed by SQL implementations in shared/services/. A singleton registry with has() guards enables composable startup — apps register their own domain service and stubs for absent domains. Absorbs glue layer: navigation, relationships, event handlers (login, container, order) now live in shared/ with has()-guarded service calls. Factory gains domain_services_fn parameter for per-app service registration. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
92
services/registry.py
Normal file
92
services/registry.py
Normal file
@@ -0,0 +1,92 @@
|
||||
"""Typed singleton registry for domain services.
|
||||
|
||||
Usage::
|
||||
|
||||
from shared.services.registry import services
|
||||
|
||||
# Register at app startup
|
||||
services.blog = SqlBlogService()
|
||||
|
||||
# Query anywhere
|
||||
if services.has("calendar"):
|
||||
entries = await services.calendar.pending_entries(session, ...)
|
||||
|
||||
# Or use stubs for absent domains
|
||||
summary = await services.cart.cart_summary(session, ...)
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from shared.contracts.protocols import (
|
||||
BlogService,
|
||||
CalendarService,
|
||||
MarketService,
|
||||
CartService,
|
||||
)
|
||||
|
||||
|
||||
class _ServiceRegistry:
|
||||
"""Central registry holding one implementation per domain.
|
||||
|
||||
Properties return the registered implementation or raise
|
||||
``RuntimeError`` if nothing is registered. Use ``has(name)``
|
||||
to check before access when the domain might be absent.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._blog: BlogService | None = None
|
||||
self._calendar: CalendarService | None = None
|
||||
self._market: MarketService | None = None
|
||||
self._cart: CartService | None = None
|
||||
|
||||
# -- blog -----------------------------------------------------------------
|
||||
@property
|
||||
def blog(self) -> BlogService:
|
||||
if self._blog is None:
|
||||
raise RuntimeError("BlogService not registered")
|
||||
return self._blog
|
||||
|
||||
@blog.setter
|
||||
def blog(self, impl: BlogService) -> None:
|
||||
self._blog = impl
|
||||
|
||||
# -- calendar -------------------------------------------------------------
|
||||
@property
|
||||
def calendar(self) -> CalendarService:
|
||||
if self._calendar is None:
|
||||
raise RuntimeError("CalendarService not registered")
|
||||
return self._calendar
|
||||
|
||||
@calendar.setter
|
||||
def calendar(self, impl: CalendarService) -> None:
|
||||
self._calendar = impl
|
||||
|
||||
# -- market ---------------------------------------------------------------
|
||||
@property
|
||||
def market(self) -> MarketService:
|
||||
if self._market is None:
|
||||
raise RuntimeError("MarketService not registered")
|
||||
return self._market
|
||||
|
||||
@market.setter
|
||||
def market(self, impl: MarketService) -> None:
|
||||
self._market = impl
|
||||
|
||||
# -- cart -----------------------------------------------------------------
|
||||
@property
|
||||
def cart(self) -> CartService:
|
||||
if self._cart is None:
|
||||
raise RuntimeError("CartService not registered")
|
||||
return self._cart
|
||||
|
||||
@cart.setter
|
||||
def cart(self, impl: CartService) -> None:
|
||||
self._cart = impl
|
||||
|
||||
# -- introspection --------------------------------------------------------
|
||||
def has(self, name: str) -> bool:
|
||||
"""Check whether a domain service is registered."""
|
||||
return getattr(self, f"_{name}", None) is not None
|
||||
|
||||
|
||||
# Module-level singleton — import this everywhere.
|
||||
services = _ServiceRegistry()
|
||||
Reference in New Issue
Block a user