Files
orion/docs/architecture/models-structure.md
Samir Boulahtit 1ad30bd77e docs: update models-structure with hybrid architecture patterns
Document the current architecture for models and schemas:
- CORE models (models/database/) vs module models (app/modules/*/models/)
- Three schema patterns: dedicated files, inline, and no-schema
- Data flow diagram showing services access DB directly
- Gap analysis table showing model-schema alignment
- Import guidelines with canonical vs legacy re-exports
- Checklists for creating new models and schemas

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 20:09:16 +01:00

14 KiB

Models & Schemas Architecture

Overview

This project uses a hybrid architecture for models and schemas:

  1. CORE models/schemas in models/database/ and models/schema/ - Framework-level entities used across modules
  2. Module models/schemas in app/modules/<module>/models/ and app/modules/<module>/schemas/ - Domain-specific entities
  3. Inline schemas defined directly in API route files - Endpoint-specific request/response models

Directory Structure

models/                              # CORE framework models
├── database/                        # SQLAlchemy models (ORM)
│   ├── __init__.py                  # Exports all models + module discovery
│   ├── base.py                      # Base class, mixins
│   ├── user.py                      # User authentication
│   ├── vendor.py                    # Vendor, VendorUser, Role
│   ├── company.py                   # Company management
│   ├── platform.py                  # Multi-platform support
│   ├── media.py                     # MediaFile (cross-cutting)
│   └── ...
│
└── schema/                          # Pydantic schemas (API validation)
    ├── __init__.py
    ├── auth.py                      # Login, tokens, password reset
    ├── vendor.py                    # Vendor CRUD schemas
    ├── company.py                   # Company schemas
    └── ...

app/modules/<module>/                # Domain modules
├── 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. CORE vs Module Models

CORE models (models/database/) are framework-level entities used across multiple modules:

  • User - Authentication, used by all modules
  • Vendor - Multi-tenant anchor, used by all vendor-scoped modules
  • Company - Business entity management
  • Platform - Multi-platform CMS support
  • MediaFile - File storage, used by catalog, CMS, etc.

Module models (app/modules/<module>/models/) are domain-specific:

  • billing/models/ - Feature, SubscriptionTier, VendorSubscription
  • catalog/models/ - Product, ProductTranslation, ProductMedia
  • orders/models/ - Order, OrderItem, Invoice
  • inventory/models/ - Inventory, InventoryTransaction

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/api/v1/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
Cross-module utility schemas Dedicated file models/schema/

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 (Preferred)

# CORE models - from models.database
from models.database import User, Vendor, MediaFile

# Module models - from app.modules.<module>.models
from app.modules.billing.models import Feature, SubscriptionTier
from app.modules.catalog.models import Product, ProductMedia
from app.modules.orders.models import Order, OrderItem

# CORE schemas - from models.schema
from models.schema.auth import LoginRequest, TokenResponse

# Module schemas - from app.modules.<module>.schemas
from app.modules.inventory.schemas import InventoryCreate, InventoryResponse
from app.modules.orders.schemas import OrderResponse

Legacy Re-exports (Backwards Compatibility)

models/database/__init__.py re-exports module models for backwards compatibility:

# These work but prefer canonical imports
from models.database import Product  # Re-exported from catalog module
from models.database import Order    # Re-exported from orders module

Gap Analysis: Models vs Schemas

Not every database model needs a dedicated schema file. Here's the current alignment:

CORE Framework

Database Model Schema Notes
user.py auth.py Auth schemas cover user operations
vendor.py vendor.py Full CRUD schemas
company.py company.py Full CRUD schemas
media.py media.py Upload/response schemas
platform.py Inline Admin-only, in platforms.py route
platform_module.py Inline Admin-only, in modules.py route
admin_menu_config.py Inline Admin-only, in menu_config.py route
vendor_email_settings.py Inline In email_settings.py route
vendor_email_template.py None Internal email service use
vendor_platform.py None Internal association table

Modules

Module Models Schemas Alignment
billing feature, subscription billing, subscription
cart cart cart
catalog product, product_media, product_translation catalog, product, vendor_product
checkout - checkout Schema-only (orchestration)
cms content_page content_page, homepage_sections
customers customer, password_reset_token customer, context (password reset uses auth schemas)
dev_tools architecture_scan, test_run Inline Admin-only, inline in route files
inventory inventory, inventory_transaction inventory (transaction included in inventory.py)
loyalty 5 models 5 schemas
marketplace 5 models 4 schemas (translation in product schema)
messaging admin_notification, message message, notification
orders order, order_item, invoice order, invoice, order_item_exception
payments - payment Schema-only (external APIs)
analytics - stats Schema-only (aggregation views)

Creating New Models

Database Model Checklist

  1. Determine location:

    • Cross-module use → models/database/
    • Module-specific → 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 app.core.database import Base
    from models.database.base import 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/api/v1/vendor/my_entity.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 schemas/
Endpoint-specific schemas Inline in route files
Internal models No schema needed
CORE models models/database/
Module models app/modules/<module>/models/