Files
orion/docs/architecture/models-structure.md
Samir Boulahtit d7a0ff8818 refactor: complete module-driven architecture migration
This commit completes the migration to a fully module-driven architecture:

## Models Migration
- Moved all domain models from models/database/ to their respective modules:
  - tenancy: User, Admin, Vendor, Company, Platform, VendorDomain, etc.
  - cms: MediaFile, VendorTheme
  - messaging: Email, VendorEmailSettings, VendorEmailTemplate
  - core: AdminMenuConfig
- models/database/ now only contains Base and TimestampMixin (infrastructure)

## Schemas Migration
- Moved all domain schemas from models/schema/ to their respective modules:
  - tenancy: company, vendor, admin, team, vendor_domain
  - cms: media, image, vendor_theme
  - messaging: email
- models/schema/ now only contains base.py and auth.py (infrastructure)

## Routes Migration
- Moved admin routes from app/api/v1/admin/ to modules:
  - menu_config.py -> core module
  - modules.py -> tenancy module
  - module_config.py -> tenancy module
- app/api/v1/admin/ now only aggregates auto-discovered module routes

## Menu System
- Implemented module-driven menu system with MenuDiscoveryService
- Extended FrontendType enum: PLATFORM, ADMIN, VENDOR, STOREFRONT
- Added MenuItemDefinition and MenuSectionDefinition dataclasses
- Each module now defines its own menu items in definition.py
- MenuService integrates with MenuDiscoveryService for template rendering

## Documentation
- Updated docs/architecture/models-structure.md
- Updated docs/architecture/menu-management.md
- Updated architecture validation rules for new exceptions

## Architecture Validation
- Updated MOD-019 rule to allow base.py in models/schema/
- Created core module exceptions.py and schemas/ directory
- All validation errors resolved (only warnings remain)

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

14 KiB

Models & Schemas Architecture

Overview

This project uses a module-based architecture for models and schemas:

  1. Infrastructure in models/database/ and models/schema/ - Base classes only
  2. Module models/schemas in app/modules/<module>/models/ and app/modules/<module>/schemas/ - All domain entities
  3. Inline schemas defined directly in API route files - Endpoint-specific request/response models

Directory Structure

models/                              # INFRASTRUCTURE ONLY
├── database/                        # SQLAlchemy base classes
│   ├── __init__.py                  # Exports Base, TimestampMixin only
│   └── base.py                      # Base class, mixins
│
└── schema/                          # Pydantic base classes
    ├── __init__.py                  # Exports base and auth only
    ├── base.py                      # Base schema classes
    └── auth.py                      # Auth schemas (cross-cutting)

app/modules/<module>/                # Domain modules (ALL domain entities)
├── models/                          # Module-specific database models
│   ├── __init__.py                  # Canonical exports
│   └── *.py                         # Model definitions
│
└── schemas/                         # Module-specific schemas
    ├── __init__.py                  # Canonical exports
    └── *.py                         # Schema definitions

Architecture Principles

1. Infrastructure vs Module Code

Infrastructure (models/database/, models/schema/) provides base classes only:

  • Base - SQLAlchemy declarative base
  • TimestampMixin - created_at/updated_at columns
  • BaseModel patterns in models/schema/base.py
  • auth.py - Authentication schemas (cross-cutting concern)

Module code (app/modules/<module>/) contains all domain-specific entities:

Module Models Schemas
tenancy User, Admin, Vendor, Company, Platform, VendorDomain company, vendor, admin, team, vendor_domain
billing Feature, SubscriptionTier, VendorSubscription billing, subscription
catalog Product, ProductTranslation, ProductMedia catalog, product, vendor_product
orders Order, OrderItem, Invoice order, invoice, order_item_exception
inventory Inventory, InventoryTransaction inventory
cms ContentPage, MediaFile, VendorTheme content_page, media, image, vendor_theme
messaging Email, VendorEmailSettings, VendorEmailTemplate, Message, Notification email, message, notification
customers Customer, PasswordResetToken customer, context
marketplace 5 models 4 schemas
core AdminMenuConfig (inline)

2. Schema Patterns

The codebase uses three valid patterns for schemas:

Pattern A: Dedicated Schema Files (Reusable)

For schemas used across multiple endpoints or modules.

# app/modules/inventory/schemas/inventory.py
class InventoryCreate(BaseModel):
    product_id: int
    location: str
    quantity: int

class InventoryResponse(BaseModel):
    model_config = ConfigDict(from_attributes=True)
    id: int
    product_id: int
    vendor_id: int
    quantity: int

Pattern B: Inline Schemas (Endpoint-Specific)

For schemas only used by a single endpoint or closely related endpoints.

# app/modules/tenancy/routes/api/admin_platforms.py
class PlatformResponse(BaseModel):
    """Platform response schema - only used in this file."""
    id: int
    code: str
    name: str
    # ...

class PlatformUpdateRequest(BaseModel):
    """Request schema - only used in this file."""
    name: str | None = None
    description: str | None = None

@router.get("/platforms/{code}", response_model=PlatformResponse)
def get_platform(...):
    ...

Pattern C: No Schema (Internal Service Use)

For models only accessed internally by services, not exposed via API.

# Internal association table - no API exposure
class VendorPlatform(Base):
    """Links vendors to platforms - internal use only."""
    __tablename__ = "vendor_platforms"
    vendor_id = Column(Integer, ForeignKey("vendors.id"))
    platform_id = Column(Integer, ForeignKey("platforms.id"))

3. When to Use Each Pattern

Scenario Pattern Location
Schema used by multiple endpoints Dedicated file app/modules/<module>/schemas/
Schema used by single endpoint Inline In the route file
Admin-only endpoint schemas Inline In the admin route file
Model not exposed via API No schema N/A

Data Flow Architecture

┌─────────────────────────────────────────────────────────────────────┐
│                           API Layer                                  │
│  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐  │
│  │ Request Schema  │───▶│    Endpoint     │───▶│ Response Schema │  │
│  │ (Pydantic)      │    │                 │    │ (Pydantic)      │  │
│  └─────────────────┘    └────────┬────────┘    └─────────────────┘  │
└──────────────────────────────────┼──────────────────────────────────┘
                                   │
                                   ▼
┌─────────────────────────────────────────────────────────────────────┐
│                         Service Layer                                │
│  ┌─────────────────────────────────────────────────────────────┐    │
│  │                    Business Logic                            │    │
│  │  • Validation      • Transformations    • Business Rules     │    │
│  └─────────────────────────────────────────────────────────────┘    │
│                                   │                                  │
│                                   ▼                                  │
│  ┌─────────────────────────────────────────────────────────────┐    │
│  │              Database Models (SQLAlchemy)                    │    │
│  │  • Direct access   • Queries          • Transactions         │    │
│  └─────────────────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────────────┘
                                   │
                                   ▼
┌─────────────────────────────────────────────────────────────────────┐
│                        Database Layer                                │
│                     PostgreSQL / SQLite                              │
└─────────────────────────────────────────────────────────────────────┘

Key principles:

  • Services access database models directly - No schema layer between service and database
  • APIs use schemas for validation - Request/response validation at API boundary
  • Schemas are API contracts - Define what clients send/receive

Import Guidelines

Canonical Imports (Required)

# Infrastructure - base classes only
from models.database import Base, TimestampMixin
from models.schema.auth import LoginRequest, TokenResponse

# Module models - from app.modules.<module>.models
from app.modules.tenancy.models import User, Vendor, Company
from app.modules.billing.models import Feature, SubscriptionTier
from app.modules.catalog.models import Product, ProductMedia
from app.modules.orders.models import Order, OrderItem
from app.modules.cms.models import MediaFile, VendorTheme
from app.modules.messaging.models import Email, VendorEmailTemplate

# Module schemas - from app.modules.<module>.schemas
from app.modules.tenancy.schemas import VendorCreate, CompanyResponse
from app.modules.cms.schemas import MediaItemResponse, VendorThemeResponse
from app.modules.messaging.schemas import EmailTemplateResponse
from app.modules.inventory.schemas import InventoryCreate, InventoryResponse
from app.modules.orders.schemas import OrderResponse

Legacy Imports (DEPRECATED)

The following import patterns are deprecated and will cause architecture validation errors:

# DEPRECATED - Don't import domain models from models.database
from models.database import User, Vendor  # WRONG

# DEPRECATED - Don't import domain schemas from models.schema
from models.schema.vendor import VendorCreate  # WRONG
from models.schema.company import CompanyResponse  # WRONG

Module Ownership Reference

Tenancy Module (app/modules/tenancy/)

Models:

  • User - User authentication and profile
  • Admin - Admin user management
  • Vendor - Vendor/merchant entities
  • VendorUser - Vendor team members
  • Company - Company management
  • Platform - Multi-platform support
  • AdminPlatform - Admin-platform association
  • VendorPlatform - Vendor-platform association
  • PlatformModule - Module configuration per platform
  • VendorDomain - Custom domain configuration

Schemas:

  • company.py - Company CRUD schemas
  • vendor.py - Vendor CRUD and Letzshop export schemas
  • admin.py - Admin user and audit log schemas
  • team.py - Team management and invitation schemas
  • vendor_domain.py - Domain configuration schemas

CMS Module (app/modules/cms/)

Models:

  • ContentPage - CMS content pages
  • MediaFile - File storage and management
  • VendorTheme - Theme customization

Schemas:

  • content_page.py - Content page schemas
  • media.py - Media upload/response schemas
  • image.py - Image handling schemas
  • vendor_theme.py - Theme configuration schemas

Messaging Module (app/modules/messaging/)

Models:

  • Email - Email records
  • VendorEmailSettings - Email configuration
  • VendorEmailTemplate - Email templates
  • Message - Internal messages
  • AdminNotification - Admin notifications

Schemas:

  • email.py - Email template schemas
  • message.py - Message schemas
  • notification.py - Notification schemas

Core Module (app/modules/core/)

Models:

  • AdminMenuConfig - Menu visibility configuration

Schemas: (inline in route files)


Creating New Models

Database Model Checklist

  1. Determine location:

    • All domain models → app/modules/<module>/models/
  2. Create the model file:

    # app/modules/mymodule/models/my_entity.py
    from sqlalchemy import Column, Integer, String, ForeignKey
    from sqlalchemy.orm import relationship
    from models.database import Base, TimestampMixin
    
    class MyEntity(Base, TimestampMixin):
        __tablename__ = "my_entities"
    
        id = Column(Integer, primary_key=True, index=True)
        vendor_id = Column(Integer, ForeignKey("vendors.id"), nullable=False)
        name = Column(String(255), nullable=False)
    
        vendor = relationship("Vendor")
    
  3. Export in __init__.py:

    # app/modules/mymodule/models/__init__.py
    from app.modules.mymodule.models.my_entity import MyEntity
    
    __all__ = ["MyEntity"]
    

Schema Checklist

  1. Decide if schema is needed:

    • Exposed via API? → Yes, create schema
    • Multiple endpoints use it? → Dedicated schema file
    • Single endpoint? → Consider inline schema
    • Internal only? → No schema needed
  2. Create schema (if needed):

    # app/modules/mymodule/schemas/my_entity.py
    from pydantic import BaseModel, ConfigDict, Field
    
    class MyEntityCreate(BaseModel):
        name: str = Field(..., min_length=1, max_length=255)
    
    class MyEntityResponse(BaseModel):
        model_config = ConfigDict(from_attributes=True)
    
        id: int
        vendor_id: int
        name: str
    
  3. Or use inline schema:

    # app/modules/mymodule/routes/api/vendor.py
    from pydantic import BaseModel
    
    class MyEntityResponse(BaseModel):
        """Response schema - only used in this file."""
        id: int
        name: str
    
    @router.get("/my-entities/{id}", response_model=MyEntityResponse)
    def get_entity(...):
        ...
    

Summary

Principle Implementation
Services access DB directly from app.modules.X.models import Model
APIs validate with schemas Request/response Pydantic models
Reusable schemas Dedicated files in app/modules/<module>/schemas/
Endpoint-specific schemas Inline in route files
Internal models No schema needed
All domain models app/modules/<module>/models/
Infrastructure only models/database/ (Base, TimestampMixin only)