# Cross-Module Import Rules

This document defines the strict import rules that ensure the module system remains decoupled, testable, and resilient. These rules are critical for maintaining a truly modular architecture.

## Core Principle

**Core modules NEVER import from optional modules.**

This is the fundamental rule that enables optional modules to be truly optional. When a core module imports from an optional module:
- The app crashes if that module is disabled
- You can't test core functionality in isolation
- You create a hidden dependency that violates the architecture

## Module Classification

### Core Modules (Always Enabled)
- `contracts` - Protocols and interfaces
- `core` - Dashboard, settings, profile
- `tenancy` - Platform, merchant, store, admin user management
- `cms` - Content pages, media library
- `customers` - Customer database
- `billing` - Subscriptions, tier limits
- `payments` - Payment gateway integrations
- `messaging` - Email, notifications

### Optional Modules (Per-Platform)
- `analytics` - Reports, dashboards
- `cart` - Shopping cart
- `catalog` - Product browsing
- `checkout` - Cart-to-order conversion
- `inventory` - Stock management
- `loyalty` - Loyalty programs
- `marketplace` - Letzshop integration
- `orders` - Order management

## Import Rules Matrix

| From \ To | Core | Optional | Contracts |
|-----------|------|----------|-----------|
| **Core** | :white_check_mark: | :x: FORBIDDEN | :white_check_mark: |
| **Optional** | :white_check_mark: | :warning: With care | :white_check_mark: |
| **Contracts** | :x: | :x: | :white_check_mark: |

### Explanation

1. **Core → Core**: Allowed. Core modules can import from each other (e.g., billing imports from tenancy)

2. **Core → Optional**: **FORBIDDEN**. This is the most important rule. Core modules must never have direct imports from optional modules.

3. **Core → Contracts**: Allowed. Contracts define shared protocols and data structures.

4. **Optional → Core**: Allowed. Optional modules can use core functionality.

5. **Optional → Optional**: Allowed with care. Check dependencies in `definition.py` to ensure proper ordering.

6. **Optional → Contracts**: Allowed. This is how optional modules implement protocols.

7. **Contracts → Anything**: Contracts should only depend on stdlib/typing/Protocol. No module imports.

## Anti-Patterns (DO NOT DO)

### Direct Import from Optional Module

```python
# app/modules/core/routes/api/admin_dashboard.py

# BAD: Core importing from optional module
from app.modules.marketplace.services import marketplace_service
from app.modules.analytics.services import stats_service

@router.get("/dashboard")
def get_dashboard():
    # This crashes if marketplace is disabled!
    imports = marketplace_service.get_recent_imports()
```

### Conditional Import with ismodule_enabled Check

```python
# BAD: Still creates import-time dependency
from app.modules.service import module_service

if module_service.is_module_enabled(db, platform_id, "marketplace"):
    from app.modules.marketplace.services import marketplace_service  # Still loaded!
    imports = marketplace_service.get_recent_imports()
```

### Type Hints from Optional Modules

```python
# BAD: Type hint forces import
from app.modules.marketplace.models import MarketplaceImportJob

def process_import(job: MarketplaceImportJob) -> None:  # Crashes if disabled
    ...
```

## Approved Patterns

### 1. Provider Protocol Pattern (Metrics & Widgets)

Use the provider protocol pattern for cross-module data:

```python
# app/modules/core/services/stats_aggregator.py

# GOOD: Import only protocols and contracts
from app.modules.contracts.metrics import MetricsProviderProtocol, MetricValue

class StatsAggregatorService:
    def _get_enabled_providers(self, db, platform_id):
        # Discover providers from MODULES registry
        from app.modules.registry import MODULES

        for module in MODULES.values():
            if module.has_metrics_provider():
                # Provider is called through protocol, not direct import
                provider = module.get_metrics_provider_instance()
                yield provider
```

### 2. Context Provider Pattern

Modules contribute context without direct imports:

```python
# app/modules/core/utils.py

def get_context_for_frontend(frontend_type, request, db, platform):
    # GOOD: Discover and call providers through registry
    for module in get_enabled_modules(db, platform.id):
        if module.has_context_provider(frontend_type):
            context.update(module.get_context_contribution(...))
```

### 3. Lazy Factory Functions

Use factory functions in module definitions:

```python
# app/modules/marketplace/definition.py

def _get_widget_provider():
    """Lazy import to avoid circular imports."""
    from app.modules.marketplace.services.marketplace_widgets import marketplace_widget_provider
    return marketplace_widget_provider

marketplace_module = ModuleDefinition(
    code="marketplace",
    widget_provider=_get_widget_provider,  # Called lazily when needed
)
```

### 4. Type Checking Only Imports

Use `TYPE_CHECKING` for type hints without runtime dependency:

```python
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from app.modules.marketplace.models import MarketplaceImportJob

def process_import(job: "MarketplaceImportJob") -> None:
    # Works at runtime even if marketplace is disabled
    ...
```

### 5. Registry-Based Discovery

Discover modules through the registry, not imports:

```python
# GOOD: Discovery through registry
from app.modules.registry import MODULES

def get_module_if_enabled(db, platform_id, module_code):
    module = MODULES.get(module_code)
    if module and module_service.is_module_enabled(db, platform_id, module_code):
        return module
    return None
```

## Architecture Validation

The architecture validator (`scripts/validate/validate_architecture.py`) includes rules to detect violations:

| Rule | Severity | Description |
|------|----------|-------------|
| IMPORT-001 | ERROR | Core module imports from optional module |
| IMPORT-002 | WARNING | Optional module imports from unrelated optional module |
| IMPORT-003 | INFO | Consider using protocol pattern instead of direct import |

Run validation:
```bash
python scripts/validate/validate_architecture.py
```

## Provider Pattern Summary

### When to Use Each Pattern

| Need | Pattern | Location |
|------|---------|----------|
| Numeric statistics | MetricsProvider | `contracts/metrics.py` |
| Dashboard widgets | WidgetProvider | `contracts/widgets.py` |
| Page template context | Context Provider | `definition.py` |
| Service access | Protocol + DI | `contracts/{module}.py` |

### Provider Flow

```
┌─────────────────────────────────────────────────────────────────────┐
│                         CONTRACTS LAYER                              │
│                     (Shared protocol definitions)                    │
│                                                                      │
│   MetricsProviderProtocol    DashboardWidgetProviderProtocol        │
│   ContentServiceProtocol     ServiceProtocol                         │
└─────────────────────────────────────────────────────────────────────┘
                                    │
                    ┌───────────────┼───────────────┐
                    ▼               ▼               ▼
        ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
        │  CORE MODULE     │ │ OPTIONAL MODULE 1│ │ OPTIONAL MODULE 2│
        │                  │ │                  │ │                  │
        │ StatsAggregator  │ │ OrderMetrics     │ │ CatalogMetrics   │
        │ WidgetAggregator │ │ (implements)     │ │ (implements)     │
        │ (discovers)      │ │                  │ │                  │
        └──────────────────┘ └──────────────────┘ └──────────────────┘
                │                       │                    │
                │                       │                    │
                ▼                       ▼                    ▼
        ┌──────────────────────────────────────────────────────────────┐
        │                      MODULE REGISTRY                          │
        │                                                               │
        │  MODULES = {                                                  │
        │      "orders": ModuleDefinition(metrics_provider=...),        │
        │      "catalog": ModuleDefinition(metrics_provider=...),       │
        │  }                                                            │
        └──────────────────────────────────────────────────────────────┘
```

## Testing Without Dependencies

Because core doesn't depend on optional modules, you can test core in isolation:

```python
# tests/unit/core/test_stats_aggregator.py

from unittest.mock import MagicMock
from app.modules.contracts.metrics import MetricValue, MetricsProviderProtocol

class MockMetricsProvider:
    """Mock that implements the protocol without needing real modules."""
    @property
    def metrics_category(self) -> str:
        return "test"

    def get_store_metrics(self, db, store_id, context=None):
        return [MetricValue(key="test.value", value=42, label="Test", category="test")]

def test_stats_aggregator_with_mock_provider():
    # Test core without any optional modules
    mock_provider = MockMetricsProvider()
    ...
```

## Consequences of Violations

### If Core Imports Optional Module

1. **Import Error**: App fails to start if module code has syntax errors
2. **Runtime Crash**: App crashes when disabled module is accessed
3. **Hidden Dependency**: Module appears optional but isn't
4. **Testing Difficulty**: Can't test core without all modules
5. **Deployment Issues**: Can't deploy minimal configurations

### Detection

Add these checks to CI:

```bash
# Check for forbidden imports
grep -r "from app.modules.marketplace" app/modules/core/ && exit 1
grep -r "from app.modules.analytics" app/modules/core/ && exit 1
grep -r "from app.modules.orders" app/modules/core/ && exit 1
```

## Summary

| Rule | Enforcement |
|------|-------------|
| Core → Optional = FORBIDDEN | Architecture validation, CI checks |
| Use Protocol pattern | Code review, documentation |
| Lazy factory functions | Required for definition.py |
| TYPE_CHECKING imports | Required for type hints across modules |
| Registry-based discovery | Required for all cross-module access |

Following these rules ensures:
- Modules can be truly enabled/disabled per platform
- Testing can be done in isolation
- New modules can be added without modifying core
- The app remains stable when modules fail

## Related Documentation

- [Module System Architecture](module-system.md) - Module structure and auto-discovery
- [Metrics Provider Pattern](metrics-provider-pattern.md) - Numeric statistics architecture
- [Widget Provider Pattern](widget-provider-pattern.md) - Dashboard widgets architecture
- [Architecture Violations Status](architecture-violations-status.md) - Current violation tracking
