# Modular Platform Architecture - Design Plan ## Executive Summary Design a modular architecture where platforms can enable/disable feature modules. This creates a hierarchy: ``` Global (SaaS Provider) └── Platform (Business Product - OMS, Loyalty, etc.) ├── Modules (Enabled features - Billing, Marketplace, Inventory, etc.) │ ├── Routes (API + Page routes) │ ├── Services (Business logic) │ ├── Menu Items (Sidebar entries) │ └── Templates (UI components) └── Frontends ├── Admin (Platform management) ├── Store (Store dashboard) └── Customer (Storefront) - future ``` --- ## Current State Analysis ### What Exists | Component | Status | Location | |-----------|--------|----------| | Platform Model | ✅ Complete | `models/database/platform.py` | | Platform Configs | ⚠️ Partial | `app/platforms/{oms,loyalty}/config.py` (routes/templates empty) | | Feature Registry | ✅ Complete | `models/database/feature.py` (50+ features) | | Feature Gating | ✅ Complete | `app/core/feature_gate.py` + `app/services/feature_service.py` | | Subscription Tiers | ✅ Complete | `models/database/subscription.py` (tier→features mapping) | | Menu System | ✅ Complete | `app/config/menu_registry.py` + `AdminMenuConfig` model | | Platform Context | ✅ Complete | `middleware/platform_context.py` (domain/path detection) | ### Key Insight: Features vs Modules **Current "Features"** = granular capabilities (e.g., `analytics_dashboard`, `letzshop_sync`) - Assigned to subscription tiers - Gated at API route level - 50+ individual features **Proposed "Modules"** = cohesive feature bundles (e.g., `billing`, `marketplace`, `inventory`) - Enabled/disabled per platform - Contains multiple features, routes, menu items - ~10-15 modules total --- ## Proposed Architecture ### Module Definition A **Module** is a self-contained unit of functionality: ```python # app/modules/base.py class ModuleDefinition: """Base class for all modules.""" # Identity code: str # "billing", "marketplace", "inventory" name: str # "Billing & Subscriptions" description: str # Dependencies requires: list[str] = [] # Other module codes required # Components features: list[str] = [] # Feature codes this module provides menu_items: dict[FrontendType, list[str]] = {} # Menu items per frontend # Routes (registered dynamically) admin_router: APIRouter | None = None store_router: APIRouter | None = None # Status is_core: bool = False # Core modules cannot be disabled ``` ### Module Registry ```python # app/modules/registry.py MODULES = { # Core modules (always enabled) "core": ModuleDefinition( code="core", name="Core Platform", is_core=True, features=["dashboard", "settings", "profile"], menu_items={ FrontendType.ADMIN: ["dashboard", "settings"], FrontendType.STORE: ["dashboard", "settings"], }, ), # Optional modules "billing": ModuleDefinition( code="billing", name="Billing & Subscriptions", features=["subscription_management", "billing_history", "stripe_integration"], menu_items={ FrontendType.ADMIN: ["subscription-tiers", "subscriptions", "billing-history"], FrontendType.STORE: ["billing"], }, admin_router=billing_admin_router, store_router=billing_store_router, ), "marketplace": ModuleDefinition( code="marketplace", name="Marketplace (Letzshop)", requires=["inventory"], # Depends on inventory module features=["letzshop_sync", "marketplace_import"], menu_items={ FrontendType.ADMIN: ["marketplace-letzshop"], FrontendType.STORE: ["letzshop", "marketplace"], }, ), "inventory": ModuleDefinition( code="inventory", name="Inventory Management", features=["inventory_basic", "inventory_locations", "low_stock_alerts"], menu_items={ FrontendType.ADMIN: ["inventory"], FrontendType.STORE: ["inventory"], }, ), # ... more modules } ``` ### Proposed Modules | Module | Description | Features | Core? | |--------|-------------|----------|-------| | `core` | Dashboard, Settings, Profile | 3 | Yes | | `platform-admin` | Merchants, Stores, Admin Users | 5 | Yes | | `billing` | Subscriptions, Tiers, Billing History | 4 | No | | `inventory` | Stock management, locations, alerts | 5 | No | | `orders` | Order management, fulfillment | 6 | No | | `marketplace` | Letzshop integration, import | 3 | No | | `customers` | Customer management, CRM | 4 | No | | `cms` | Content pages, media library | 6 | No | | `analytics` | Dashboard, reports, exports | 4 | No | | `messaging` | Internal messages, notifications | 3 | No | | `dev-tools` | Components, icons (internal) | 2 | No | | `monitoring` | Logs, background tasks, imports | 4 | No | --- ## Database Changes ### Option A: JSON Field (Simpler) Use existing `Platform.settings` JSON field: ```python # Platform.settings example { "enabled_modules": ["core", "billing", "inventory", "orders"], "module_config": { "billing": {"stripe_mode": "live"}, "inventory": {"low_stock_threshold": 10} } } ``` **Pros:** No migration needed, flexible **Cons:** No referential integrity, harder to query ### Option B: Junction Table (Recommended) New `PlatformModule` model: ```python # models/database/platform_module.py class PlatformModule(Base, TimestampMixin): """Module enablement per platform.""" __tablename__ = "platform_modules" id = Column(Integer, primary_key=True) platform_id = Column(Integer, ForeignKey("platforms.id"), nullable=False) module_code = Column(String(50), nullable=False) is_enabled = Column(Boolean, default=True) config = Column(JSON, default={}) # Module-specific config enabled_at = Column(DateTime) enabled_by_user_id = Column(Integer, ForeignKey("users.id")) __table_args__ = ( UniqueConstraint("platform_id", "module_code"), ) ``` **Pros:** Proper normalization, audit trail, queryable **Cons:** Requires migration --- ## Implementation Phases ### Phase 1: Module Registry (No DB Changes) 1. Create `app/modules/` directory structure: ``` app/modules/ ├── __init__.py ├── base.py # ModuleDefinition class ├── registry.py # MODULES dict └── service.py # ModuleService ``` 2. Define all modules in registry (data only, no behavior change) 3. Create `ModuleService`: ```python class ModuleService: def get_platform_modules(platform_id: int) -> list[str] def is_module_enabled(platform_id: int, module_code: str) -> bool def get_module_menu_items(platform_id: int, frontend_type: FrontendType) -> list[str] ``` 4. Initially read from `Platform.settings["enabled_modules"]` (Option A) ### Phase 2: Integrate with Menu System 1. Update `MenuService.get_menu_for_rendering()`: - Filter menu items based on enabled modules - Module-disabled items don't appear (not just hidden) 2. Update `AdminMenuConfig` logic: - Can only configure visibility for module-enabled items - Module-disabled items are completely removed ### Phase 3: Database Model (Optional) 1. Create `PlatformModule` model 2. Migration to create table 3. Migrate data from `Platform.settings["enabled_modules"]` 4. Update `ModuleService` to use new table ### Phase 4: Dynamic Route Registration 1. Modify `app/api/v1/admin/__init__.py`: ```python def register_module_routes(app: FastAPI, platform_code: str): enabled_modules = module_service.get_enabled_modules(platform_code) for module in enabled_modules: if module.admin_router: app.include_router(module.admin_router) ``` 2. Add module check middleware for routes ### Phase 5: Admin UI for Module Management 1. Create `/admin/platforms/{code}/modules` page 2. Toggle modules on/off per platform 3. Show module dependencies 4. Module-specific configuration --- ## Directory Structure Evolution ### Current ``` app/ ├── api/v1/ │ ├── admin/ # All admin routes mixed │ └── store/ # All store routes mixed ├── platforms/ │ ├── oms/config.py # Platform config only │ └── loyalty/config.py └── services/ # All services mixed ``` ### Proposed (Gradual Migration) ``` app/ ├── modules/ │ ├── base.py │ ├── registry.py │ ├── service.py │ ├── core/ # Core module │ │ ├── __init__.py │ │ └── definition.py │ ├── billing/ # Billing module │ │ ├── __init__.py │ │ ├── definition.py │ │ ├── routes/ │ │ │ ├── admin.py │ │ │ └── store.py │ │ └── services/ │ │ └── subscription_service.py │ ├── marketplace/ # Marketplace module │ │ └── ... │ └── inventory/ # Inventory module │ └── ... ├── api/v1/ # Legacy routes (gradually migrate) └── platforms/ # Platform-specific overrides ├── oms/ └── loyalty/ ``` --- ## Key Design Decisions Needed ### 1. Migration Strategy | Option | Description | |--------|-------------| | **A: Big Bang** | Move all code to modules at once | | **B: Gradual** | Keep existing structure, modules are metadata only initially | | **C: Hybrid** | New features in modules, migrate existing over time | **Recommendation:** Option C (Hybrid) - Start with module definitions as metadata, then gradually move code. ### 2. Module Granularity | Option | Example | |--------|---------| | **Coarse** | 5-8 large modules (billing, operations, content) | | **Medium** | 10-15 medium modules (billing, inventory, orders, cms) | | **Fine** | 20+ small modules (subscription-tiers, invoices, stock-levels) | **Recommendation:** Medium granularity - matches current menu sections. ### 3. Core vs Optional Which modules should be mandatory (cannot be disabled)? **Proposed Core:** - `core` (dashboard, settings) - `platform-admin` (merchants, stores, admin-users) **Everything else optional** (including billing - some platforms may not charge). --- ## Relationship to Existing Systems ### Modules → Features - Module contains multiple features - Enabling module makes its features available for tier assignment - Features still gated by subscription tier ### Modules → Menu Items - Module specifies which menu items it provides - Menu items only visible if module enabled AND menu visibility allows ### Modules → Routes - Module can provide admin and store routers - Routes only registered if module enabled - Existing `require_menu_access()` still applies ### Platform Config → Modules - `app/platforms/oms/config.py` can specify default modules - Database `PlatformModule` or `Platform.settings` overrides defaults --- ## Verification Plan 1. **Module definition only (Phase 1)** - Define all modules in registry - Add `enabled_modules` to Platform.settings - Verify ModuleService returns correct modules 2. **Menu integration (Phase 2)** - Disable "billing" module for Loyalty platform - Verify billing menu items don't appear in sidebar - Verify `/admin/subscriptions` returns 404 or redirect 3. **Full module isolation (Phase 4)** - Create new platform with minimal modules - Verify only enabled module routes are accessible - Verify module dependencies are enforced --- ## Decisions Made | Decision | Choice | Rationale | |----------|--------|-----------| | **Storage** | JSON field (`Platform.settings`) | No migration needed, can upgrade to table later | | **Migration** | Gradual | Module definitions as metadata first, migrate code over time | | **Billing** | Optional module | Some platforms may not charge (e.g., internal loyalty) | | **First module** | `billing` | Self-contained, clear routes/services, good isolation test | --- ## Implementation Roadmap ### Immediate (Phase 1): Module Foundation 1. Create `app/modules/` directory with base classes 2. Define module registry with all ~12 modules 3. Create `ModuleService` reading from `Platform.settings` 4. Add `enabled_modules` to OMS and Loyalty platform settings ### Next (Phase 2): Menu Integration 1. Update `MenuService` to filter by enabled modules 2. Test: Disable billing module → billing menu items disappear ### Then (Phase 3): Billing Module Extraction 1. Create `app/modules/billing/` structure 2. Move billing routes and services into module 3. Register billing routes dynamically based on module status ### Future: Additional Modules - Extract marketplace, inventory, orders, etc. - Consider junction table if audit trail becomes important --- ## Files to Create/Modify | File | Action | Purpose | |------|--------|---------| | `app/modules/__init__.py` | CREATE | Module package init | | `app/modules/base.py` | CREATE | ModuleDefinition dataclass | | `app/modules/registry.py` | CREATE | MODULES dict with all definitions | | `app/modules/service.py` | CREATE | ModuleService class | | `app/services/menu_service.py` | MODIFY | Filter by enabled modules | | `app/platforms/oms/config.py` | MODIFY | Add enabled_modules | | `app/platforms/loyalty/config.py` | MODIFY | Add enabled_modules |