- Add observability framework documentation (health checks, metrics, Sentry) - Add developer guide for creating modules - Add comprehensive module migration plan with Celery task integration - Update architecture overview with module system and observability sections - Update module-system.md with links to new docs Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
484 lines
12 KiB
Markdown
484 lines
12 KiB
Markdown
# Creating Modules
|
|
|
|
This guide explains how to create new modules for the Wizamart platform, including both simple wrapper modules and self-contained modules with their own services, models, and migrations.
|
|
|
|
## Module Types
|
|
|
|
| Type | Complexity | Use Case |
|
|
|------|------------|----------|
|
|
| **Wrapper Module** | Simple | Groups existing routes and features under a toggleable module |
|
|
| **Self-Contained Module** | Complex | Full isolation with own services, models, templates, migrations |
|
|
|
|
## Quick Start: Wrapper Module
|
|
|
|
For a simple module that wraps existing functionality:
|
|
|
|
```python
|
|
# app/modules/analytics/definition.py
|
|
from app.modules.base import ModuleDefinition
|
|
from models.database.admin_menu_config import FrontendType
|
|
|
|
analytics_module = ModuleDefinition(
|
|
code="analytics",
|
|
name="Analytics & Reports",
|
|
description="Business analytics, reports, and dashboards",
|
|
version="1.0.0",
|
|
features=[
|
|
"analytics_dashboard",
|
|
"sales_reports",
|
|
"customer_insights",
|
|
],
|
|
menu_items={
|
|
FrontendType.ADMIN: ["analytics-dashboard", "reports"],
|
|
FrontendType.VENDOR: ["analytics", "sales-reports"],
|
|
},
|
|
is_core=False,
|
|
is_internal=False,
|
|
)
|
|
```
|
|
|
|
## Module Definition Fields
|
|
|
|
### Required Fields
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `code` | str | Unique identifier (e.g., "billing", "analytics") |
|
|
| `name` | str | Display name (e.g., "Billing & Subscriptions") |
|
|
|
|
### Optional Fields
|
|
|
|
| Field | Type | Default | Description |
|
|
|-------|------|---------|-------------|
|
|
| `description` | str | "" | What the module provides |
|
|
| `version` | str | "1.0.0" | Semantic version |
|
|
| `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 |
|
|
| `is_core` | bool | False | Cannot be disabled if True |
|
|
| `is_internal` | bool | False | Admin-only if True |
|
|
|
|
### Configuration Fields
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `config_schema` | type[BaseModel] | Pydantic model for validation |
|
|
| `default_config` | dict | Default configuration values |
|
|
|
|
### Lifecycle Hooks
|
|
|
|
| Field | Signature | Description |
|
|
|-------|-----------|-------------|
|
|
| `on_enable` | (platform_id: int) -> None | Called when enabled |
|
|
| `on_disable` | (platform_id: int) -> None | Called when disabled |
|
|
| `on_startup` | () -> None | Called on app startup |
|
|
| `health_check` | () -> dict | Returns health status |
|
|
|
|
## Module Classification
|
|
|
|
### Core Modules
|
|
|
|
Cannot be disabled. Use for essential platform functionality.
|
|
|
|
```python
|
|
core_module = ModuleDefinition(
|
|
code="tenancy",
|
|
name="Platform Tenancy",
|
|
is_core=True, # Cannot be disabled
|
|
# ...
|
|
)
|
|
```
|
|
|
|
### Optional Modules
|
|
|
|
Can be enabled/disabled per platform.
|
|
|
|
```python
|
|
billing_module = ModuleDefinition(
|
|
code="billing",
|
|
name="Billing",
|
|
is_core=False, # Can be toggled
|
|
is_internal=False,
|
|
# ...
|
|
)
|
|
```
|
|
|
|
### Internal Modules
|
|
|
|
Admin-only tools not visible to customers/vendors.
|
|
|
|
```python
|
|
devtools_module = ModuleDefinition(
|
|
code="dev-tools",
|
|
name="Developer Tools",
|
|
is_internal=True, # Admin-only
|
|
# ...
|
|
)
|
|
```
|
|
|
|
## Module Dependencies
|
|
|
|
Declare dependencies using the `requires` field:
|
|
|
|
```python
|
|
# orders module requires payments
|
|
orders_module = ModuleDefinition(
|
|
code="orders",
|
|
name="Orders",
|
|
requires=["payments"], # Must have payments enabled
|
|
# ...
|
|
)
|
|
```
|
|
|
|
**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
|
|
|
|
## Self-Contained Module Structure
|
|
|
|
For modules 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/
|
|
│ ├── __init__.py
|
|
│ ├── admin.py # Admin API routes
|
|
│ └── vendor.py # Vendor API routes
|
|
├── services/
|
|
│ ├── __init__.py
|
|
│ └── content_service.py
|
|
├── models/
|
|
│ ├── __init__.py
|
|
│ └── content_page.py
|
|
├── schemas/
|
|
│ ├── __init__.py
|
|
│ └── content.py
|
|
├── templates/
|
|
│ ├── admin/
|
|
│ └── vendor/
|
|
├── migrations/
|
|
│ └── versions/
|
|
│ ├── cms_001_create_content_pages.py
|
|
│ └── cms_002_add_seo_fields.py
|
|
└── locales/
|
|
├── en.json
|
|
└── fr.json
|
|
```
|
|
|
|
### Self-Contained Definition
|
|
|
|
```python
|
|
# app/modules/cms/definition.py
|
|
from pydantic import BaseModel, Field
|
|
from app.modules.base import ModuleDefinition
|
|
from models.database.admin_menu_config import FrontendType
|
|
|
|
class CMSConfig(BaseModel):
|
|
"""CMS module configuration."""
|
|
max_pages_per_vendor: int = Field(default=100, ge=1, le=1000)
|
|
enable_seo: bool = True
|
|
default_language: str = "en"
|
|
|
|
cms_module = ModuleDefinition(
|
|
# Identity
|
|
code="cms",
|
|
name="Content Management",
|
|
description="Content pages, media library, and themes",
|
|
version="1.0.0",
|
|
|
|
# Classification
|
|
is_core=True,
|
|
is_self_contained=True,
|
|
|
|
# Features
|
|
features=[
|
|
"content_pages",
|
|
"media_library",
|
|
"vendor_themes",
|
|
],
|
|
|
|
# Menu items
|
|
menu_items={
|
|
FrontendType.ADMIN: ["content-pages", "vendor-themes"],
|
|
FrontendType.VENDOR: ["content-pages", "media"],
|
|
},
|
|
|
|
# Configuration
|
|
config_schema=CMSConfig,
|
|
default_config={
|
|
"max_pages_per_vendor": 100,
|
|
"enable_seo": True,
|
|
},
|
|
|
|
# Self-contained paths
|
|
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",
|
|
|
|
# Lifecycle
|
|
health_check=lambda: {"status": "healthy"},
|
|
)
|
|
```
|
|
|
|
## Creating Module Routes
|
|
|
|
### Admin Routes
|
|
|
|
```python
|
|
# app/modules/payments/routes/admin.py
|
|
from fastapi import APIRouter, Depends
|
|
from sqlalchemy.orm import Session
|
|
from app.api.deps import get_db, get_current_admin_user
|
|
|
|
admin_router = APIRouter(prefix="/api/admin/payments", tags=["Admin - Payments"])
|
|
|
|
@admin_router.get("/gateways")
|
|
async def list_gateways(
|
|
db: Session = Depends(get_db),
|
|
current_user = Depends(get_current_admin_user),
|
|
):
|
|
"""List configured payment gateways."""
|
|
# Implementation
|
|
pass
|
|
```
|
|
|
|
### Vendor Routes
|
|
|
|
```python
|
|
# app/modules/payments/routes/vendor.py
|
|
from fastapi import APIRouter, Depends
|
|
from app.api.deps import get_db, get_current_vendor_user
|
|
|
|
vendor_router = APIRouter(prefix="/api/vendor/payments", tags=["Vendor - Payments"])
|
|
|
|
@vendor_router.get("/methods")
|
|
async def list_payment_methods(
|
|
db: Session = Depends(get_db),
|
|
current_user = Depends(get_current_vendor_user),
|
|
):
|
|
"""List vendor's stored payment methods."""
|
|
pass
|
|
```
|
|
|
|
### Lazy Router Loading
|
|
|
|
To avoid circular imports, use lazy loading:
|
|
|
|
```python
|
|
# app/modules/payments/definition.py
|
|
|
|
def _get_admin_router():
|
|
"""Lazy import to avoid circular imports."""
|
|
from app.modules.payments.routes.admin import admin_router
|
|
return admin_router
|
|
|
|
def _get_vendor_router():
|
|
from app.modules.payments.routes.vendor import vendor_router
|
|
return vendor_router
|
|
|
|
payments_module = ModuleDefinition(
|
|
code="payments",
|
|
# ...
|
|
)
|
|
|
|
def get_payments_module_with_routers():
|
|
"""Get module with routers attached."""
|
|
payments_module.admin_router = _get_admin_router()
|
|
payments_module.vendor_router = _get_vendor_router()
|
|
return payments_module
|
|
```
|
|
|
|
## Module Migrations
|
|
|
|
### Naming Convention
|
|
|
|
```
|
|
{module_code}_{sequence}_{description}.py
|
|
```
|
|
|
|
Examples:
|
|
- `cms_001_create_content_pages.py`
|
|
- `cms_002_add_seo_fields.py`
|
|
- `billing_001_create_subscriptions.py`
|
|
|
|
### Migration Template
|
|
|
|
```python
|
|
# app/modules/cms/migrations/versions/cms_001_create_content_pages.py
|
|
"""Create content pages table.
|
|
|
|
Revision ID: cms_001
|
|
Revises:
|
|
Create Date: 2026-01-27
|
|
|
|
"""
|
|
from alembic import op
|
|
import sqlalchemy as sa
|
|
|
|
revision = "cms_001"
|
|
down_revision = None # Or previous module migration
|
|
branch_labels = ("cms",)
|
|
depends_on = None
|
|
|
|
def upgrade() -> None:
|
|
op.create_table(
|
|
"cms_content_pages",
|
|
sa.Column("id", sa.Integer(), primary_key=True),
|
|
sa.Column("vendor_id", sa.Integer(), sa.ForeignKey("vendors.id")),
|
|
sa.Column("title", sa.String(200), nullable=False),
|
|
sa.Column("slug", sa.String(200), nullable=False),
|
|
sa.Column("content", sa.Text()),
|
|
sa.Column("created_at", sa.DateTime(timezone=True)),
|
|
sa.Column("updated_at", sa.DateTime(timezone=True)),
|
|
)
|
|
|
|
def downgrade() -> None:
|
|
op.drop_table("cms_content_pages")
|
|
```
|
|
|
|
## Module Configuration
|
|
|
|
### Defining Configuration Schema
|
|
|
|
```python
|
|
from pydantic import BaseModel, Field
|
|
|
|
class BillingConfig(BaseModel):
|
|
"""Billing module configuration."""
|
|
|
|
stripe_mode: Literal["test", "live"] = "test"
|
|
trial_days: int = Field(default=14, ge=0, le=365)
|
|
enable_invoices: bool = True
|
|
invoice_prefix: str = Field(default="INV-", max_length=10)
|
|
```
|
|
|
|
### Using Configuration
|
|
|
|
```python
|
|
from app.modules.service import module_service
|
|
|
|
# Get module config for platform
|
|
config = module_service.get_module_config(db, platform_id, "billing")
|
|
# Returns: {"stripe_mode": "test", "trial_days": 14, ...}
|
|
|
|
# Set module config
|
|
module_service.set_module_config(
|
|
db, platform_id, "billing",
|
|
{"trial_days": 30}
|
|
)
|
|
```
|
|
|
|
## Health Checks
|
|
|
|
### Defining Health Check
|
|
|
|
```python
|
|
def check_billing_health() -> dict:
|
|
"""Check billing service dependencies."""
|
|
issues = []
|
|
|
|
# Check Stripe
|
|
try:
|
|
stripe.Account.retrieve()
|
|
except stripe.AuthenticationError:
|
|
issues.append("Stripe authentication failed")
|
|
|
|
if issues:
|
|
return {
|
|
"status": "unhealthy",
|
|
"message": "; ".join(issues),
|
|
}
|
|
|
|
return {
|
|
"status": "healthy",
|
|
"details": {"stripe": "connected"},
|
|
}
|
|
|
|
billing_module = ModuleDefinition(
|
|
code="billing",
|
|
health_check=check_billing_health,
|
|
# ...
|
|
)
|
|
```
|
|
|
|
## Registering the Module
|
|
|
|
### Add to Registry
|
|
|
|
```python
|
|
# app/modules/registry.py
|
|
|
|
from app.modules.analytics.definition import analytics_module
|
|
|
|
OPTIONAL_MODULES: dict[str, ModuleDefinition] = {
|
|
# ... existing modules
|
|
"analytics": analytics_module,
|
|
}
|
|
|
|
MODULES = {**CORE_MODULES, **OPTIONAL_MODULES, **INTERNAL_MODULES}
|
|
```
|
|
|
|
## Module Events
|
|
|
|
Subscribe to module lifecycle events:
|
|
|
|
```python
|
|
from app.modules.events import module_event_bus, ModuleEvent, ModuleEventData
|
|
|
|
@module_event_bus.subscribe(ModuleEvent.ENABLED)
|
|
def on_analytics_enabled(data: ModuleEventData):
|
|
if data.module_code == "analytics":
|
|
# Initialize analytics for this platform
|
|
setup_analytics_dashboard(data.platform_id)
|
|
|
|
@module_event_bus.subscribe(ModuleEvent.DISABLED)
|
|
def on_analytics_disabled(data: ModuleEventData):
|
|
if data.module_code == "analytics":
|
|
# Cleanup
|
|
cleanup_analytics_data(data.platform_id)
|
|
```
|
|
|
|
## Checklist
|
|
|
|
### New Module Checklist
|
|
|
|
- [ ] Create module directory: `app/modules/{code}/`
|
|
- [ ] Create `definition.py` with ModuleDefinition
|
|
- [ ] Add module to appropriate dict in `registry.py`
|
|
- [ ] Create routes if needed (admin.py, vendor.py)
|
|
- [ ] Register menu items in menu registry
|
|
- [ ] Create migrations if adding database tables
|
|
- [ ] Add health check if module has dependencies
|
|
- [ ] Document features and configuration options
|
|
- [ ] Write tests for module functionality
|
|
|
|
### Self-Contained Module Checklist
|
|
|
|
- [ ] All items from basic checklist
|
|
- [ ] Create `services/` directory with business logic
|
|
- [ ] Create `models/` directory with SQLAlchemy models
|
|
- [ ] Create `schemas/` directory with Pydantic schemas
|
|
- [ ] Create `exceptions.py` for module-specific errors
|
|
- [ ] Create `config.py` with Pydantic config model
|
|
- [ ] Set up `migrations/versions/` directory
|
|
- [ ] Create `templates/` if module has UI
|
|
- [ ] Create `locales/` if module needs translations
|
|
- [ ] Set `is_self_contained=True` and path attributes
|
|
|
|
## Related Documentation
|
|
|
|
- [Module System](../architecture/module-system.md) - Architecture overview
|
|
- [Menu Management](../architecture/menu-management.md) - Menu item integration
|
|
- [Database Migrations](migration/database-migrations.md) - Migration guide
|