# Module System Architecture The Wizamart platform uses a modular architecture that allows features to be enabled or disabled per platform. This document explains the module system, its classification tiers, and how modules interact with the rest of the application. ## Overview ``` ┌─────────────────────────────────────────────────────────────────────┐ │ FRAMEWORK LAYER │ │ (Infrastructure that modules depend on - not modules themselves) │ │ │ │ Config │ Database │ Auth │ Permissions │ Observability │ Celery │ └─────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ MODULE LAYER │ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ CORE MODULES (Always Enabled) │ │ │ │ core │ tenancy │ cms │ customers │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ OPTIONAL MODULES (Per-Platform) │ │ │ │ payments │ billing │ inventory │ orders │ marketplace │ ...│ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ INTERNAL MODULES (Admin Only) │ │ │ │ dev-tools │ monitoring │ │ │ └─────────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────────┘ ``` ## Three-Tier Classification ### Core Modules (4) Core modules are **always enabled** and cannot be disabled. They provide fundamental platform functionality. | Module | Description | Key Features | |--------|-------------|--------------| | `core` | Dashboard, settings, profile | Basic platform operation | | `tenancy` | Platform, company, vendor, admin user management | Multi-tenant infrastructure | | `cms` | Content pages, media library, themes | Content management | | `customers` | Customer database, profiles, segmentation | Customer data management | ### Optional Modules (7) Optional modules can be **enabled or disabled per platform**. They provide additional functionality that may not be needed by all platforms. | Module | Dependencies | Description | |--------|--------------|-------------| | `payments` | - | Payment gateway integrations (Stripe, PayPal, etc.) | | `billing` | `payments` | Platform subscriptions, vendor invoices | | `inventory` | - | Stock management, locations | | `orders` | `payments` | Order management, customer checkout | | `marketplace` | `inventory` | Letzshop integration | | `analytics` | - | Reports, dashboards | | `messaging` | - | Messages, notifications | ### Internal Modules (2) Internal modules are **admin-only tools** not exposed to customers or vendors. | Module | Description | |--------|-------------| | `dev-tools` | Component library, icon browser | | `monitoring` | Logs, background tasks, Flower, Grafana integration | ## Framework Layer The Framework Layer provides infrastructure that modules depend on. These are **not modules** - they're always available and cannot be disabled. | Component | Location | Purpose | |-----------|----------|---------| | Config | `app/core/config.py` | Settings management | | Database | `app/core/database.py` | SQLAlchemy sessions | | Logging | `app/core/logging.py` | Structured logging | | Permissions | `app/core/permissions.py` | RBAC definitions | | Feature Gate | `app/core/feature_gate.py` | Tier-based access | | Celery | `app/core/celery_config.py` | Task queue | | Observability | `app/core/observability.py` | Health checks, metrics, Sentry | | Auth Middleware | `middleware/auth.py` | JWT authentication | | Context Middleware | `middleware/platform_context.py` | Multi-tenancy | | Dependencies | `app/api/deps.py` | FastAPI DI | | Base Exceptions | `app/exceptions/base.py` | Exception hierarchy | ## Module Definition Each module is defined using the `ModuleDefinition` dataclass: ```python from app.modules.base import ModuleDefinition from models.database.admin_menu_config import FrontendType billing_module = ModuleDefinition( # Identity code="billing", name="Billing & Subscriptions", description="Platform subscriptions and vendor invoices", version="1.0.0", # Dependencies requires=["payments"], # Must have payments enabled # Features (for tier-based gating) features=[ "subscription_management", "billing_history", "invoice_generation", ], # Menu items per frontend menu_items={ FrontendType.ADMIN: ["subscription-tiers", "subscriptions"], FrontendType.VENDOR: ["billing", "invoices"], }, # Classification is_core=False, is_internal=False, # Configuration schema (optional) config_schema=BillingConfig, default_config={"trial_days": 14}, # Lifecycle hooks (optional) on_enable=lambda platform_id: setup_billing(platform_id), on_disable=lambda platform_id: cleanup_billing(platform_id), health_check=lambda: {"status": "healthy"}, ) ``` ### ModuleDefinition Fields | Field | Type | Description | |-------|------|-------------| | `code` | `str` | Unique identifier (e.g., "billing") | | `name` | `str` | Display name | | `description` | `str` | What the module provides | | `version` | `str` | Semantic version (default: "1.0.0") | | `requires` | `list[str]` | Module codes this depends on | | `features` | `list[str]` | Feature codes for tier gating | | `menu_items` | `dict` | Menu items per frontend type | | `permissions` | `list[str]` | Permission codes defined by module | | `is_core` | `bool` | Cannot be disabled if True | | `is_internal` | `bool` | Admin-only if True | | `config_schema` | `type[BaseModel]` | Pydantic model for configuration | | `default_config` | `dict` | Default configuration values | | `on_enable` | `Callable` | Called when module is enabled | | `on_disable` | `Callable` | Called when module is disabled | | `on_startup` | `Callable` | Called on application startup | | `health_check` | `Callable` | Returns health status dict | | `migrations_path` | `str` | Path to module migrations (relative) | ## Module Dependencies Modules can depend on other modules. When enabling a module, its dependencies are automatically enabled. ``` payments ↙ ↘ billing orders inventory ↓ marketplace ``` **Dependency Rules:** 1. Core modules cannot depend on optional modules 2. Enabling a module auto-enables its dependencies 3. Disabling a module auto-disables modules that depend on it 4. Circular dependencies are not allowed ## Module Registry All modules are registered in `app/modules/registry.py`: ```python from app.modules.registry import ( MODULES, # All modules CORE_MODULES, # Core only OPTIONAL_MODULES, # Optional only INTERNAL_MODULES, # Internal only get_module, get_core_module_codes, get_module_tier, is_core_module, ) # Get a specific module billing = get_module("billing") # Check module tier tier = get_module_tier("billing") # Returns "optional" # Get all core module codes core_codes = get_core_module_codes() # {"core", "tenancy", "cms", "customers"} ``` ## Module Service The `ModuleService` manages module enablement per platform: ```python from app.modules.service import module_service # Check if module is enabled if module_service.is_module_enabled(db, platform_id, "billing"): # Module is enabled for this platform pass # Get all enabled modules for a platform modules = module_service.get_platform_modules(db, platform_id) # Enable a module (auto-enables dependencies) module_service.enable_module(db, platform_id, "billing", user_id=current_user.id) # Disable a module (auto-disables dependents) module_service.disable_module(db, platform_id, "billing", user_id=current_user.id) # Get module configuration config = module_service.get_module_config(db, platform_id, "billing") # Set module configuration module_service.set_module_config(db, platform_id, "billing", {"trial_days": 30}) ``` ## Module Events The event system allows components to react to module lifecycle changes: ```python from app.modules.events import module_event_bus, ModuleEvent, ModuleEventData # Subscribe to events @module_event_bus.subscribe(ModuleEvent.ENABLED) def on_module_enabled(data: ModuleEventData): print(f"Module {data.module_code} enabled for platform {data.platform_id}") @module_event_bus.subscribe(ModuleEvent.DISABLED) def on_module_disabled(data: ModuleEventData): clear_module_cache(data.platform_id, data.module_code) @module_event_bus.subscribe(ModuleEvent.CONFIG_CHANGED) def on_config_changed(data: ModuleEventData): print(f"Config changed: {data.config}") # Events are emitted by ModuleService automatically # You can also emit manually: module_event_bus.emit_enabled("billing", platform_id=1, user_id=42) ``` ### Event Types | Event | When Fired | Data Available | |-------|------------|----------------| | `ENABLED` | Module enabled for platform | `module_code`, `platform_id`, `user_id` | | `DISABLED` | Module disabled for platform | `module_code`, `platform_id`, `user_id` | | `STARTUP` | Application starting | `module_code` | | `SHUTDOWN` | Application shutting down | `module_code` | | `CONFIG_CHANGED` | Module config updated | `module_code`, `platform_id`, `config` | ## Module-Specific Migrations Self-contained modules can have their own database migrations: ``` app/modules/cms/ ├── migrations/ │ └── versions/ │ ├── cms_001_create_content_pages.py │ └── cms_002_add_seo_fields.py ├── models/ ├── services/ └── ... ``` **Migration Naming Convention:** ``` {module_code}_{sequence}_{description}.py ``` Alembic automatically discovers module migrations: ```python # In alembic/env.py from app.modules.migrations import get_all_migration_paths version_locations = [str(p) for p in get_all_migration_paths()] ``` ## Self-Contained Module Structure Modules can be self-contained with their own services, models, and templates: ``` app/modules/cms/ ├── __init__.py ├── definition.py # ModuleDefinition ├── config.py # Configuration schema (optional) ├── exceptions.py # Module-specific exceptions ├── routes/ │ ├── admin.py # Admin API routes │ └── vendor.py # Vendor API routes ├── services/ │ └── content_service.py ├── models/ │ └── content_page.py ├── schemas/ │ └── content.py # Pydantic schemas ├── templates/ │ ├── admin/ │ └── vendor/ ├── migrations/ │ └── versions/ └── locales/ ├── en.json └── fr.json ``` Configure paths in the definition: ```python cms_module = ModuleDefinition( code="cms", name="Content Management", is_self_contained=True, services_path="app.modules.cms.services", models_path="app.modules.cms.models", schemas_path="app.modules.cms.schemas", templates_path="templates", exceptions_path="app.modules.cms.exceptions", locales_path="locales", migrations_path="migrations", ) ``` ## Database Storage Module enablement is stored in the `platform_modules` table: | Column | Type | Description | |--------|------|-------------| | `platform_id` | FK | Platform reference | | `module_code` | string | Module identifier | | `is_enabled` | boolean | Whether enabled | | `enabled_at` | timestamp | When enabled | | `enabled_by_user_id` | FK | Who enabled it | | `disabled_at` | timestamp | When disabled | | `disabled_by_user_id` | FK | Who disabled it | | `config` | JSON | Module-specific configuration | ## Menu Item Filtering Menu items are filtered based on enabled modules: ```python from app.modules.service import module_service from models.database.admin_menu_config import FrontendType # Get available menu items for platform menu_items = module_service.get_module_menu_items( db, platform_id, FrontendType.ADMIN ) # Check if specific menu item's module is enabled is_available = module_service.is_menu_item_module_enabled( db, platform_id, "subscription-tiers", FrontendType.ADMIN ) ``` ## Health Checks Modules can provide health checks that are aggregated: ```python from app.core.observability import health_registry, register_module_health_checks # Register all module health checks on startup register_module_health_checks() # Health endpoint aggregates results # GET /health returns: { "status": "healthy", "checks": [ {"name": "module:billing", "status": "healthy"}, {"name": "module:payments", "status": "healthy"}, ... ] } ``` ## Best Practices ### Do - Keep modules focused on a single domain - Use `requires` for hard dependencies - Provide `health_check` for critical modules - Use events for cross-module communication - Document module features and menu items ### Don't - Create circular dependencies - Make core modules depend on optional modules - Put framework-level code in modules - Skip migration naming conventions - Forget to register menu items ## Related Documentation - [Menu Management](menu-management.md) - Sidebar and menu configuration - [Multi-Tenant System](multi-tenant.md) - Platform isolation - [Feature Gating](../implementation/feature-gating-system.md) - Tier-based access