Project Structure
Pattern 2 β Structured App Layout
Section titled βPattern 2 β Structured App LayoutβFor applications with a handful of domain services, explicit providers, and YAML config.
my-app/βββ application.yaml # Configurationβββ pyproject.tomlβββ src/ βββ my_app/ βββ __init__.py βββ app.py # create_app() β composition root β βββ providers/ β βββ app_provider.py # Domain service registration β βββ domain/ β βββ models.py # Entity / ValueObject classes β βββ services.py # Business logic β βββ api/ βββ controllers/ βββ user_controller.pyKey Files
Section titled βKey Filesβ| File | Purpose |
|---|---|
app.py | The composition root β create_app() factory that wires providers. Auto-detected by lexigram run |
application.yaml | Configuration β loaded by LexigramConfig.from_yaml() |
providers/ | Provider subclasses that bind services in the DI container via register() |
domain/ | Business logic β framework-agnostic models and services |
api/controllers/ | Controller subclasses auto-discovered by WebProvider.auto_discover() |
Example app.py
Section titled βExample app.pyβfrom lexigram import Application, LexigramConfigfrom lexigram.web import WebProviderfrom my_app.providers.app_provider import AppProvider
def create_app() -> Application: config = LexigramConfig.from_yaml() app = Application(name="my-app", config=config)
app.add_provider(AppProvider()) app.add_provider(WebProvider.auto_discover("my_app.api.controllers"))
return appExample Provider
Section titled βExample Providerβfrom lexigram.di.provider import Providerfrom lexigram.contracts.core import ProviderPriorityfrom lexigram.contracts.core.di import ContainerRegistrarProtocol
class AppProvider(Provider): name = "app" priority = ProviderPriority.DOMAIN
async def register(self, container: ContainerRegistrarProtocol) -> None: from my_app.domain.services import UserService
container.singleton(UserService, UserService)Example Controller
Section titled βExample Controllerβfrom lexigram.web import Controller, get, postfrom lexigram.result import Resultfrom my_app.domain.services import UserServicefrom my_app.domain.models import User
class UserController(Controller): prefix = "/api/v1/users"
def __init__(self, user_service: UserService) -> None: self.user_service = user_service # injected by the container
@get("/") async def list_users(self) -> list[User]: return await self.user_service.list_all()
@get("/{user_id}") async def get_user(self, user_id: str) -> Result[User, str]: return await self.user_service.find(user_id)Pattern 3 β Modular App Layout
Section titled βPattern 3 β Modular App LayoutβFor multi-team applications with module boundaries, import/export visibility, and environment profiles.
my-platform/βββ application.yaml # Base configβββ application.production.yaml # Profile overrideβββ application.staging.yamlβββ pyproject.tomlβββ src/ βββ my_platform/ βββ __init__.py βββ app.py # create_app() β composition root β βββ infrastructure/ β βββ __init__.py # @module InfraModule β βββ db_provider.py β βββ cache_provider.py β βββ modules/ βββ auth/ β βββ __init__.py # @module AuthModule (boundary) β βββ protocols.py # AuthServiceProtocol (the contract) β βββ provider.py # AuthProvider β βββ services.py # AuthService (implementation) β βββ controllers/ β βββ auth_controller.py β βββ billing/ βββ __init__.py # @module BillingModule βββ protocols.py βββ provider.py βββ services.pyKey Conventions
Section titled βKey Conventionsβ| Convention | Why |
|---|---|
__init__.py is the module boundary | Contains the @module class β the public API of the package |
protocols.py defines the contract | Other modules import protocols, never concrete implementations |
exports=[ ] controls visibility | Only exported types are accessible to importing modules |
providers/ stays internal | Providers register services but are never imported by other modules |
Example Module
Section titled βExample Moduleβfrom lexigram.di.module import module
from my_platform.modules.auth.provider import AuthProviderfrom my_platform.modules.auth.protocols import AuthServiceProtocol
@module( providers=[AuthProvider], exports=[AuthServiceProtocol],)class AuthModule: """Authentication β only AuthServiceProtocol is visible to importers."""Example App With Modules
Section titled βExample App With Modulesβfrom lexigram import Application, LexigramConfig, CoreModulefrom lexigram.web import WebModule
from my_platform.infrastructure import InfraModulefrom my_platform.modules.auth import AuthModulefrom my_platform.modules.billing import BillingModule
def create_app(profile: str | None = None) -> Application: config = LexigramConfig.from_env_profile(profile) app = Application(name="my-platform", config=config)
app.add_module(CoreModule) app.add_module(InfraModule) app.add_module(AuthModule) app.add_module(BillingModule) app.add_module(WebModule.auto_discover("my_platform.modules"))
return appYou can also mix standalone providers with modules in the same app:
app.add_module(AuthModule) # module with boundariesapp.add_provider(MetricsProvider()) # standalone β globally visibleProvider Discovery
Section titled βProvider DiscoveryβInstead of manually importing every provider, use discover_providers() to scan packages:
app = Application(name="my-app", config=config)app.discover_providers("my_app.modules", "my_app.infrastructure")This scans each package recursively for Provider subclasses and @injectable / @singleton classes, then auto-registers them.
Next Steps
Section titled βNext Stepsβ- Core Concepts β Providers, DI, Result type, and modules in depth
- Configuration β YAML config, profiles, and auto-injection