Files
orion/docs/architecture/module-system.md
Samir Boulahtit e3cab29c1a docs: add module system and menu management architecture documentation
Add comprehensive documentation for:
- Module system architecture (three-tier classification, registry, events, migrations)
- Menu management system (registry, visibility config, module integration)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 22:21:23 +01:00

15 KiB

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:

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:

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:

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:

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:

# 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:

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:

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:

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