Modular Monolith Architecture¶
How to structure a Python application with spryx-di using ports & adapters per module.
Structure¶
modules/
├── identity/
│ ├── ports.py # UserReader (ABC), UserRepository (ABC)
│ ├── adapters.py # PgUserReader, PgUserRepository
│ └── module.py # Module definition
├── orders/
│ ├── ports.py # OrderRepository (ABC)
│ ├── adapters.py # PgOrderRepository
│ ├── handlers.py # CreateOrderHandler (zero DI imports)
│ └── module.py # Module definition
app/
└── main.py # Composition root
Module Definition¶
Each module registers its own providers and declares what it exports:
# modules/identity/module.py
from spryx_di import ClassProvider, Module
identity_module = Module(
name="identity",
providers=[
ClassProvider(provide=UserRepository, use_class=PgUserRepository),
ClassProvider(provide=UserReader, use_class=PgUserReader),
],
exports=[UserReader],
)
Cross-Module Dependencies¶
Handlers declare dependencies via type hints. No DI imports needed:
# modules/orders/handlers.py
class CreateOrderHandler:
def __init__(self, repo: OrderRepository, reader: UserReader) -> None:
self._repo = repo
self._reader = reader
The orders module imports identity to get access to UserReader:
# modules/orders/module.py
orders_module = Module(
name="orders",
providers=[ClassProvider(provide=OrderRepository, use_class=PgOrderRepository)],
imports=[identity_module],
)
Composition Root¶
One file that wires everything:
# app/main.py
from spryx_di import ApplicationContext, ValueProvider
ctx = ApplicationContext(
modules=[identity_module, orders_module],
globals=[ValueProvider(provide=Database, use_value=db)],
)
handler = ctx.resolve(CreateOrderHandler)
The handler gets OrderRepository from its own module, UserReader from identity (via import), and Database from globals. All auto-wired.