# Session Note: Module Dependency Redesign **Date:** 2026-02-03 **Status:** To be continued **Priority:** High - Architecture blocker --- ## Summary We discovered that while billing, payments, and messaging have been correctly reclassified as core modules, **the app would still crash if optional modules are removed** due to hard imports from core → optional modules. The fundamental issue is **flawed architecture**: core modules should never depend on optional modules. The dependency direction is backwards. --- ## Work Completed Today ### 1. Module Reclassification Made billing, payments, and messaging core modules: - `app/modules/billing/definition.py` - `is_core=True` - `app/modules/payments/definition.py` - `is_core=True` - `app/modules/messaging/definition.py` - `is_core=True` ### 2. Letzshop Export Routes Moved (T5b) Moved export routes from tenancy to marketplace where they belong: - **Old:** `GET/POST /api/v1/admin/stores/{id}/export/letzshop` - **New:** `GET/POST /api/v1/admin/letzshop/stores/{id}/export` Files changed: - `app/modules/marketplace/routes/api/admin_letzshop.py` - `app/modules/marketplace/schemas/letzshop.py` - `app/modules/tenancy/routes/api/admin_stores.py` - `app/modules/tenancy/schemas/store.py` - Tests and documentation updated ### 3. Documentation Updated - `docs/architecture/module-system.md` - `docs/proposals/decouple-modules.md` - `docs/testing/admin-frontend-features.md` - `docs/guides/letzshop-admin-management.md` --- ## Critical Issue: Core → Optional Dependencies ### Current State (BROKEN) The app crashes on startup with `ImportError` if optional modules are removed. #### tenancy (core) → analytics (optional) ```python # tenancy/routes/api/admin_stores.py (lines 20, 23) from app.modules.analytics.services.stats_service import stats_service # TOP-LEVEL from app.modules.analytics.schemas import StoreStatsResponse # TOP-LEVEL ``` #### tenancy (core) → marketplace (optional) ```python # tenancy/models/__init__.py (line 22) from app.modules.marketplace.models.marketplace_import_job import MarketplaceImportJob # TOP-LEVEL # tenancy/services/store_service.py (lines 19, 26) from app.modules.marketplace.exceptions import MarketplaceProductNotFoundException from app.modules.marketplace.models import MarketplaceProduct ``` #### tenancy (core) → catalog (optional) ```python # tenancy/services/store_service.py (lines 18, 27, 30) from app.modules.catalog.exceptions import ProductAlreadyExistsException from app.modules.catalog.models import Product from app.modules.catalog.schemas import ProductCreate ``` #### billing (core) → catalog (optional) ```python # billing/services/subscription_service.py (line 48) from app.modules.catalog.models import Product # billing/services/admin_subscription_service.py (line 30) from app.modules.catalog.models import Product # billing/services/capacity_forecast_service.py (line 19) from app.modules.catalog.models import Product ``` --- ## The Design Flaw ### Current (Wrong) ``` Core Modules ──imports──> Optional Modules ↓ CRASH if optional removed ``` ### Correct Design ``` Optional Modules ──extends/provides to──> Core Modules ↓ Graceful degradation if optional removed ``` --- ## Root Cause Analysis | Violation | Why It's Wrong | What Should Happen | |-----------|----------------|-------------------| | tenancy imports `stats_service` | Core shouldn't know analytics exists | Analytics registers a MetricsProvider; core discovers it | | tenancy imports `MarketplaceImportJob` | Core shouldn't know marketplace exists | Marketplace owns its relationships entirely | | tenancy's store_service creates products | Product creation is catalog's domain | Move this code to catalog module | | billing imports `Product` to count | Billing shouldn't query catalog tables | Catalog provides count via ProductCountProvider protocol | --- ## Proposed Solution: Provider Pattern ### Architecture ``` ┌─────────────────────────────────────────────────────────┐ │ CORE MODULES │ │ (Define protocols/hooks, never import from optional) │ │ │ │ contracts: Define protocols (MetricsProvider, etc.) │ │ core: Discover and aggregate providers │ │ tenancy: Store management (no product knowledge) │ │ billing: Tier limits (ask "count?" via protocol) │ └─────────────────────────────────────────────────────────┘ ▲ │ implements/provides │ ┌─────────────────────────────────────────────────────────┐ │ OPTIONAL MODULES │ │ (Extend core by implementing protocols/hooks) │ │ │ │ catalog: Implements ProductCountProvider │ │ analytics: Implements MetricsProvider │ │ marketplace: Owns import jobs, provides to tenancy │ └─────────────────────────────────────────────────────────┘ ``` ### Example: Billing Needs Product Count **Current (wrong):** ```python # billing/services/subscription_service.py from app.modules.catalog.models import Product # CRASH if catalog removed def get_product_count(store_id): return db.query(Product).filter(Product.store_id == store_id).count() ``` **Proposed (correct):** ```python # contracts/capacity.py class ProductCountProvider(Protocol): def get_product_count(self, db: Session, store_id: int) -> int: ... # catalog/services/product_count_provider.py class CatalogProductCountProvider: def get_product_count(self, db, store_id): return db.query(Product).filter(...).count() # Register in catalog/definition.py catalog_module = ModuleDefinition( product_count_provider=_get_product_count_provider, ... ) # billing/services/subscription_service.py def get_product_count(db, store_id, platform_id): provider = get_product_count_provider(db, platform_id) # Discovers from enabled modules if provider: return provider.get_product_count(db, store_id) return 0 # Graceful fallback ``` --- ## Questions to Resolve Tomorrow 1. **What belongs where?** - Does product creation in `tenancy/store_service.py` belong in catalog? - Should `MarketplaceImportJob` relationships stay on User/Store or move entirely to marketplace? 2. **Provider patterns needed:** - `MetricsProvider` (already proposed) - for dashboard stats - `ProductCountProvider` - for billing tier limits - `ImportJobProvider` - for marketplace import jobs? 3. **Migration strategy:** - Move code first, or create protocols first? - How to handle the User/Store ↔ MarketplaceImportJob relationship? 4. **Testing:** - How to verify the app runs with optional modules removed? - Integration test that imports only core modules? --- ## Module Classification Reference ### Core Modules (8) | Module | Purpose | |--------|---------| | contracts | Protocol definitions | | core | Dashboard, settings | | tenancy | Platform, merchant, store, user management | | cms | Content pages, media, themes | | customers | Customer database | | billing | Subscriptions, tier limits | | payments | Payment gateways | | messaging | Email, notifications | ### Optional Modules (8) | Module | Purpose | |--------|---------| | analytics | Reports, dashboards | | cart | Shopping cart | | catalog | Product catalog | | checkout | Order placement | | inventory | Stock management | | loyalty | Loyalty programs | | marketplace | Letzshop integration | | orders | Order management | ### Internal Modules (2) | Module | Purpose | |--------|---------| | dev-tools | Component library | | monitoring | Logs, tasks | --- ## Files to Review Tomorrow 1. `app/modules/tenancy/services/store_service.py` - Product creation code 2. `app/modules/tenancy/models/__init__.py` - MarketplaceImportJob import 3. `app/modules/billing/services/subscription_service.py` - Product count queries 4. `app/modules/contracts/` - Existing protocols to extend --- ## Next Steps 1. Design the provider protocols needed 2. Decide what code moves where 3. Implement the provider pattern for one case (e.g., ProductCountProvider) 4. Test that core modules can load without optional modules 5. Apply pattern to remaining violations