Some checks failed
- Add redis-exporter container to docker-compose (oliver006/redis_exporter, 32MB) - Add Redis scrape target to Prometheus config - Add 4 Redis alert rules: RedisDown, HighMemory, HighConnections, RejectedConnections - Document Step 19b (Sentry Error Tracking) in Hetzner deployment guide - Document Step 19c (Redis Monitoring) in Hetzner deployment guide - Update resource budget and port reference tables Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
433 lines
13 KiB
Markdown
433 lines
13 KiB
Markdown
# 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 |
|