Files
orion/docs/architecture/cross-module-migration-plan.md
Samir Boulahtit 30c4593e0f
Some checks failed
CI / ruff (push) Successful in 9s
CI / pytest (push) Has been cancelled
CI / validate (push) Has been cancelled
CI / dependency-scanning (push) Has been cancelled
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled
refactor(P6): standardize route variable naming to router
All route files (admin.py, store.py) now export `router` instead of
`admin_router`/`store_router`. Consumer code (definition.py, __init__.py)
imports as `router as admin_router` where distinction is needed.
ModuleDefinition fields remain admin_router/store_router.

64 files changed across all modules. Architecture rules, docs, and
migration plan updated. Added noqa:API001 support to validator for
pre-existing raw dict endpoints now visible with standardized router name.
All 1114 tests pass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 11:05:34 +01:00

22 KiB

Cross-Module Import Migration Plan

Created: 2026-02-26 Updated: 2026-02-27 Status: Complete (Service Layer) — Cat 1-4 fully migrated Rules: MOD-025, MOD-026

This document tracks the migration of all cross-module model imports to proper service-based access patterns.

Overview

Category Description Files Priority Status
Cat 5 UserContext legacy import path 74 URGENT Pending
Cat 1 Direct queries on another module's models ~47 URGENT DONE
Cat 2 Creating instances across module boundaries ~15 URGENT DONE
Cat 3 Aggregation/count queries across boundaries ~11 URGENT DONE
Cat 4 Join queries involving another module's models ~4 URGENT DONE
P5 Provider pattern gaps (widgets, metrics) ~8 modules Incremental Pending
P6 Route variable naming standardization ~70 files Low DONE

Completed Service-Layer Migration (2026-02-27)

Result: Zero top-level cross-module model imports remain in any service file. All 1114 tests pass.

Patterns Used

Pattern When Used Files
Service method call Owning module has/got a service method Most files — replaced db.query(Model) with some_service.get_by_id()
from __future__ import annotations + TYPE_CHECKING Type hints only, no runtime usage invoice_service.py, marketplace_import_job_service.py, stripe_service.py, etc.
Method-body deferred import 1-2 methods need the model product_service.py, product_media_service.py, platform_settings_service.py
_get_model() helper 3+ methods use same infrastructure model log_service.py, admin_audit_service.py, admin_settings_service.py, admin_notification_service.py, platform_service.py
Instance-cached self._Model Model used in nearly every method letzshop/order_service.py (Order/OrderItem)
joinedload() replacement Replaced .join(Model) with eager loading via ORM relationship inventory_service.py, admin_audit_service.py
Pre-query ID filtering Get IDs from service, then Model.id.in_(ids) All *_metrics.py, *_features.py files (StorePlatform → platform_service.get_store_ids_for_platform())

See Cross-Module Import Rules for detailed pattern documentation.

New Service Methods Added

Module Method Purpose
tenancy/platform_service get_default_platform(db) Returns first platform
tenancy/platform_service get_primary_platform_id_for_store(db, store_id) Primary platform ID for a store
tenancy/store_service list_all_stores(db, active_only) All stores (with optional active filter)
tenancy/store_service is_letzshop_slug_claimed(db, slug) Check if Letzshop slug is claimed
tenancy/store_service is_store_code_taken(db, code) Check store code uniqueness
tenancy/store_service is_subdomain_taken(db, subdomain) Check subdomain uniqueness
tenancy/admin_service get_user_by_email(db, email) Lookup user by email
tenancy/admin_service get_user_by_username(db, username) Lookup user by username
billing/subscription_service get_all_active_subscriptions(db) All active/trial subscriptions
catalog/product_service get_products_with_gtin(db, store_id) Products that have GTINs
inventory/inventory_service delete_inventory_by_gtin(db, gtin) Delete inventory by GTIN
inventory/inventory_service get_inventory_by_gtin(db, gtin) Get inventory records by GTIN
marketplace/import_job_service get_import_job_stats(db) Import job statistics with processing/today counts

Files Migrated (by module)

catalog/ (5 files): catalog_metrics.py, catalog_features.py, product_service.py, product_media_service.py, store_product_service.py

orders/ (4 files): order_metrics.py, order_features.py, order_item_exception_service.py, order_inventory_service.py

inventory/ (3 files): inventory_metrics.py, inventory_service.py, inventory_import_service.py

marketplace/ (8 files): marketplace_widgets.py, marketplace_product_service.py, marketplace_import_job_service.py, onboarding_service.py, platform_signup_service.py, letzshop_export_service.py, letzshop/order_service.py, letzshop/store_sync_service.py

monitoring/ (5 files): admin_audit_service.py, audit_provider.py, background_tasks_service.py, capacity_forecast_service.py, log_service.py, platform_health_service.py

messaging/ (3 files): email_service.py, store_email_settings_service.py, admin_notification_service.py

cms/ (3 files): cms_metrics.py, store_theme_service.py, content_page_service.py

core/ (2 files): admin_settings_service.py, platform_settings_service.py

customers/ (2 files): customer_metrics.py, admin_customer_service.py

tenancy/ (1 file): platform_service.py

cart/ (1 file): cart_service.py


Cat 5: Move UserContext to Tenancy Module (74 files)

Priority: URGENT — mechanical, low risk, high impact Approach: Move definition, update all imports in one batch

What

UserContext is defined in models/schema/auth.py (legacy location). Per MOD-019, schemas belong in their module. UserContext is a tenancy concern (user identity, platform/store context).

Steps

  1. Move UserContext class from models/schema/auth.py to app/modules/tenancy/schemas/auth.py

    • Keep all properties and methods intact
    • Also move related schemas used alongside it: UserLogin, LogoutResponse, StoreUserResponse
  2. Add re-export in legacy location (temporary backwards compat):

    # models/schema/auth.py
    from app.modules.tenancy.schemas.auth import UserContext  # noqa: F401
    
  3. Update all 74 import sites from:

    from models.schema.auth import UserContext
    

    to:

    from app.modules.tenancy.schemas.auth import UserContext
    
  4. Remove legacy re-export once all imports are updated

Files to Update (by module)

app/api/ (1 file)

  • app/api/deps.py

app/modules/tenancy/ (15 files)

  • routes/api/admin_merchants.py
  • routes/api/admin_module_config.py
  • routes/api/admin_merchant_domains.py
  • routes/api/admin_modules.py
  • routes/api/admin_platforms.py
  • routes/api/admin_store_domains.py
  • routes/api/admin_stores.py
  • routes/api/admin_users.py
  • routes/api/merchant.py
  • routes/api/store_auth.py (also imports LogoutResponse, StoreUserResponse, UserLogin)
  • routes/api/store_profile.py
  • routes/api/store_team.py
  • routes/pages/merchant.py
  • services/admin_platform_service.py
  • tests/integration/test_merchant_routes.py

app/modules/core/ (12 files)

  • routes/api/admin_dashboard.py
  • routes/api/admin_menu_config.py
  • routes/api/admin_settings.py
  • routes/api/merchant_menu.py
  • routes/api/store_dashboard.py
  • routes/api/store_menu.py
  • routes/api/store_settings.py
  • routes/pages/merchant.py
  • tests/integration/test_merchant_dashboard_routes.py
  • tests/integration/test_merchant_menu_routes.py
  • tests/integration/test_store_dashboard_routes.py

app/modules/billing/ (9 files)

  • routes/api/admin.py
  • routes/api/admin_features.py
  • routes/api/store.py
  • routes/api/store_addons.py
  • routes/api/store_checkout.py
  • routes/api/store_features.py
  • routes/api/store_usage.py
  • routes/pages/merchant.py
  • tests/integration/test_merchant_routes.py

app/modules/marketplace/ (8 files)

  • routes/api/admin_letzshop.py
  • routes/api/admin_marketplace.py
  • routes/api/admin_products.py
  • routes/api/store_letzshop.py
  • routes/api/store_marketplace.py
  • routes/api/store_onboarding.py
  • tests/integration/test_store_page_routes.py
  • tests/unit/test_store_page_routes.py

app/modules/messaging/ (7 files)

  • routes/api/admin_email_templates.py
  • routes/api/admin_messages.py
  • routes/api/admin_notifications.py
  • routes/api/store_email_settings.py
  • routes/api/store_email_templates.py
  • routes/api/store_messages.py
  • routes/api/store_notifications.py

app/modules/orders/ (6 files)

  • routes/api/admin.py
  • routes/api/admin_exceptions.py
  • routes/api/store.py
  • routes/api/store_customer_orders.py
  • routes/api/store_exceptions.py
  • routes/api/store_invoices.py

app/modules/monitoring/ (6 files)

  • routes/api/admin_audit.py
  • routes/api/admin_code_quality.py
  • routes/api/admin_logs.py
  • routes/api/admin_platform_health.py
  • routes/api/admin_tasks.py
  • routes/api/admin_tests.py

app/modules/cms/ (4 files)

  • routes/api/admin_images.py
  • routes/api/admin_media.py
  • routes/api/admin_store_themes.py
  • routes/api/store_media.py

app/modules/catalog/ (2 files)

  • routes/api/admin.py
  • routes/api/store.py

app/modules/customers/ (2 files)

  • routes/api/admin.py
  • routes/api/store.py

app/modules/inventory/ (2 files)

  • routes/api/admin.py
  • routes/api/store.py

app/modules/loyalty/ (2 files)

  • routes/pages/merchant.py
  • tests/conftest.py

app/modules/payments/ (1 file)

  • routes/api/store.py

tests/ (1 file)

  • tests/unit/api/test_deps.py

docs/ (2 files — update code examples)

  • docs/architecture/user-context-pattern.md
  • docs/proposals/decouple-modules.md

Cat 1: Direct Queries → Service Methods (~47 violations)

Priority: URGENT — requires creating new service methods first Approach: Per-module, add service methods then migrate consumers

Required New Service Methods

Tenancy Module (most consumed)

The tenancy module needs these public service methods for cross-module consumers:

# app/modules/tenancy/services/merchant_service.py (new or extend existing)
class MerchantService:
    def get_merchant_by_id(self, db, merchant_id) -> Merchant | None
    def get_merchant_by_owner_id(self, db, owner_user_id) -> Merchant | None
    def get_merchants_for_platform(self, db, platform_id, **filters) -> list[Merchant]
    def get_merchant_count(self, db, platform_id=None) -> int
    def search_merchants(self, db, query, platform_id=None) -> list[Merchant]

# app/modules/tenancy/services/store_service.py (extend existing)
class StoreService:
    def get_store_by_id(self, db, store_id) -> Store | None
    def get_stores_for_merchant(self, db, merchant_id) -> list[Store]
    def get_stores_for_platform(self, db, platform_id, **filters) -> list[Store]
    def get_store_count(self, db, merchant_id=None, platform_id=None) -> int
    def get_active_store_count(self, db, platform_id) -> int

# app/modules/tenancy/services/platform_service.py (extend existing)
class PlatformService:
    def get_platform_by_id(self, db, platform_id) -> Platform | None
    def list_platforms(self, db) -> list[Platform]

# app/modules/tenancy/services/user_service.py (extend existing)
class UserService:
    def get_user_by_id(self, db, user_id) -> User | None
    def get_user_by_email(self, db, email) -> User | None
    def get_store_users(self, db, store_id) -> list[StoreUser]

Catalog Module

# app/modules/catalog/services/product_service.py (extend existing)
class ProductService:
    def get_product_by_id(self, db, product_id) -> Product | None
    def get_products_by_ids(self, db, product_ids) -> list[Product]
    def get_product_count(self, db, store_id=None) -> int

Orders Module

# app/modules/orders/services/order_service.py (extend existing)
class OrderService:
    def get_order_by_id(self, db, order_id) -> Order | None
    def get_order_count(self, db, store_id=None, **filters) -> int
    def get_orders_for_store(self, db, store_id, **filters) -> list[Order]

Migration Per Consuming Module

billing → tenancy (13 direct queries)

File What it queries Replace with
services/admin_subscription_service.py:279 db.query(Platform) platform_service.get_platform_by_id()
services/admin_subscription_service.py:285 db.query(Store) store_service.get_store_by_id()
services/admin_subscription_service.py:362 db.query(Store).filter() store_service.get_stores_for_merchant()
services/billing_service.py:158 db.query(Store) store_service.get_store_by_id()
services/billing_service.py:497 db.query(Merchant) merchant_service.get_merchant_by_id()
services/feature_service.py:118,145 db.query(StorePlatform) store_service.get_store_platform()
services/store_platform_sync_service.py:14 db.query(StorePlatform) store_service methods
services/stripe_service.py:26,297,316 db.query(Merchant/Store) merchant_service/store_service
services/subscription_service.py:56,74,178,191 db.query(Platform/Store) service methods
services/usage_service.py:22 db.query(Store) store_service

loyalty → tenancy (10 direct queries)

File What it queries Replace with
services/card_service.py db.query(Store/User) store_service/user_service
services/program_service.py db.query(Merchant) merchant_service
services/admin_loyalty_service.py db.query(Merchant) merchant_service

marketplace → tenancy (5 direct queries)

File What it queries Replace with
services/letzshop/order_service.py:76 db.query(Store) store_service.get_store_by_id()
services/letzshop/order_service.py:100,110 db.query(Order) order_service.get_order_count()
services/marketplace_metrics.py:203 db.query(StorePlatform) store_service.get_store_count()

core → tenancy (3 direct queries)

File What it queries Replace with
services/auth_service.py:156 db.query(Merchant) merchant_service.get_merchant_by_owner_id()
services/auth_service.py:25 db.query(Store) store_service.get_store_by_id()
services/menu_service.py:351 db.query(Store).join() store_service.get_stores_for_merchant()

analytics → tenancy/catalog (4 direct queries)

File What it queries Replace with
services/stats_service.py:69 db.query(Product) product_service.get_product_count()
services/stats_service.py:75 db.query(Product) product_service methods
services/stats_service.py:88-92 db.query(MarketplaceProduct) marketplace service
services/stats_service.py:229 db.query(Product) aggregation product_service

Other modules (various)

Module File Replace with
inventory services/inventory_transaction_service.py:236 order_service.get_order_by_id()
cms services/cms_features.py:160 store_service.get_store_count()
customers services/admin_customer_service.py:16 store_service.get_store_by_id()

Cat 2: Model Creation Across Boundaries (~15 violations)

Priority: URGENT Approach: Add create/factory methods to owning services

Most of these are in test fixtures (acceptable exception) but a few are in production code:

Production Code Violations

File Creates Replace with
contracts/audit.py:117 AdminAuditLog() Use audit provider log_action()
billing/services/store_platform_sync_service.py StorePlatform() store_service.create_store_platform()
marketplace/services/letzshop/order_service.py Order/OrderItem() order_service.create_order()

Test Fixtures (Acceptable — Document as Exception)

Test files creating models from other modules for integration test setup is acceptable but should be documented:

# ACCEPTABLE in tests — document with comment:
# Test fixture: creates tenancy models for integration test setup
merchant = Merchant(name="Test", ...)

Cat 3: Aggregation/Count Queries (~11 violations)

Priority: URGENT Approach: Add count/stats methods to owning services

File Query Replace with
marketplace/services/letzshop/order_service.py:100 func.count(Order.id) order_service.get_order_count()
marketplace/services/letzshop/order_service.py:110 func.count(Order.id) order_service.get_order_count()
catalog/services/catalog_features.py:92 StorePlatform count store_service.get_active_store_count()
cms/services/cms_features.py:160 StorePlatform count store_service.get_active_store_count()
marketplace/services/marketplace_metrics.py:203 StorePlatform count store_service.get_active_store_count()
customers/services/customer_features.py Store count store_service.get_store_count()
inventory/services/inventory_features.py Store count store_service.get_store_count()
orders/services/order_features.py Store count store_service.get_store_count()

Cat 4: Join Queries (~4 violations)

Priority: URGENT Approach: Decompose into service calls or add service methods

File Join Resolution
catalog/services/store_product_service.py:19 .join(Store) Use store_service.get_store_by_id() + own query
core/services/menu_service.py:351 .join(Store) store_service.get_stores_for_merchant()
customers/services/admin_customer_service.py:16 .join(Store) store_service.get_store_by_id() + own query
messaging/services/messaging_service.py:653 .join(StoreUser) user_service.get_store_users() + filter

P5: Provider Pattern Gaps (Incremental)

Priority: MEDIUM — implement as each module is touched

Widget Providers to Add

Currently only 2 modules (marketplace, tenancy) provide dashboard widgets. These modules have valuable dashboard data:

Module Widget Ideas Implementation
orders Recent orders list, order status breakdown services/order_widgets.py
billing Subscription status, revenue trend services/billing_widgets.py
catalog Product summary, category breakdown services/catalog_widgets.py
inventory Stock summary, low-stock alerts services/inventory_widgets.py
loyalty Program stats, member engagement services/loyalty_widgets.py
customers Customer growth, segments services/customer_widgets.py

Metrics Providers to Add

Module Metric Ideas Implementation
loyalty Active programs, total enrollments, points issued services/loyalty_metrics.py
payments Transaction volume, success rate, gateway stats services/payment_metrics.py
analytics Report count, active dashboards services/analytics_metrics.py

Implementation Template

# app/modules/{module}/services/{module}_widgets.py
from app.modules.contracts.widgets import (
    DashboardWidgetProviderProtocol,
    ListWidget,
    BreakdownWidget,
)

class {Module}WidgetProvider:
    @property
    def widget_category(self) -> str:
        return "{module}"

    def get_store_widgets(self, db, store_id, context=None):
        # Return list of ListWidget/BreakdownWidget
        ...

    def get_platform_widgets(self, db, platform_id, context=None):
        ...

{module}_widget_provider = {Module}WidgetProvider()

Register in definition.py:

def _get_widget_provider():
    from app.modules.{module}.services.{module}_widgets import {module}_widget_provider
    return {module}_widget_provider

{module}_module = ModuleDefinition(
    ...
    widget_provider=_get_widget_provider,
)

P6: Route Variable Naming — DONE (2026-02-27)

Priority: LOW — cosmetic, no functional impact Files Changed: ~70 (25 route files + 40 definition/init files + architecture rules + docs)

All route files now export router instead of admin_router/store_router. Consumer code (definition.py, __init__.py) imports as router as admin_router where distinction is needed. The ModuleDefinition dataclass fields remain admin_router/store_router.

Pattern:

# Route file (admin.py, store.py) — uses `router`
router = APIRouter(prefix="/billing", ...)

# definition.py — imports as `router`, assigns to distinct field
def _get_admin_router():
    from app.modules.billing.routes.api.admin import router
    return router

billing_module.admin_router = _get_admin_router()

Execution Order

Phase 1: Foundation (Do First) — DONE

  1. Cat 5: Move UserContext to tenancy.schemas.auth — Pending (separate task)
  2. Add service methods to tenancyDONE (2026-02-27)

Phase 2: High-Impact Migrations — DONE (2026-02-27)

  1. Cat 1 - billing→tenancy: 13 violations — DONE
  2. Cat 1 - loyalty→tenancy: 10 violations — DONE
  3. Cat 1 - marketplace→tenancy/catalog/orders: 10 violations — DONE
  4. Cat 1 - core→tenancy: 3 violations — DONE
  5. Cat 1 - analytics→tenancy/catalog: 4 violations — DONE

Phase 3: Remaining Migrations — DONE (2026-02-27)

  1. Cat 2: Model creation violations — DONE (deferred imports in method bodies)
  2. Cat 3: All aggregation queries — DONE (service calls + pre-query ID filtering)
  3. Cat 4: All join queries — DONE (joinedload + service calls)
  4. Cat 1: Remaining modules — DONE (all modules migrated)

Phase 4: Provider Enrichment (Incremental)

  1. P5: Add widget providers to orders, billing, catalog (highest value)
  2. P5: Add metrics providers to loyalty, payments
  3. P5: Add remaining widget providers as modules are touched

Phase 5: Cleanup — DONE (2026-02-27)

  1. Cat 5: Move UserContext to tenancy.schemas.auth (74 files)DONE (commits 4aa6f76, e3a52f6)
  2. P6: Route variable naming standardizationDONE (all route files export router)

Testing Strategy

For each migration:

  1. Add/verify service method has a unit test
  2. Run existing integration tests to confirm no regressions
  3. Run python scripts/validate/validate_architecture.py to verify violation count decreases