docs: migrate module documentation to single source of truth
Move 39 documentation files from top-level docs/ into each module's docs/ folder, accessible via symlinks from docs/modules/. Create data-model.md files for 10 modules with full schema documentation. Replace originals with redirect stubs. Remove empty guide stubs. Modules migrated: tenancy, billing, loyalty, marketplace, orders, messaging, cms, catalog, inventory, hosting, prospecting. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
345
app/modules/orders/docs/architecture.md
Normal file
345
app/modules/orders/docs/architecture.md
Normal file
@@ -0,0 +1,345 @@
|
||||
# Customer-Orders Architecture
|
||||
|
||||
This document describes the consumer-agnostic customer architecture, following the same pattern as [Media Architecture](../../architecture/media-architecture.md).
|
||||
|
||||
## Overview
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ CONSUMER MODULES │
|
||||
│ │
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||||
│ │ Orders │ │ Loyalty │ │ Future │ │
|
||||
│ │ │ │ (future) │ │ Module │ │
|
||||
│ │ Order model │ │LoyaltyPoints│ │ XxxCustomer │ │
|
||||
│ │ (customer_id)│ │(customer_id)│ │ │ │
|
||||
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
|
||||
│ │ │ │ │
|
||||
│ └──────────────────┼──────────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌─────────────────────────────────────────────────────────────┐ │
|
||||
│ │ Customers Module │ │
|
||||
│ │ │ │
|
||||
│ │ Customer (generic, consumer-agnostic storage) │ │
|
||||
│ │ CustomerService (CRUD, authentication, profile management) │ │
|
||||
│ └─────────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Design Principles
|
||||
|
||||
### 1. Consumer-Agnostic Customer Storage
|
||||
|
||||
The customers module provides **generic customer storage** without knowing what entities will reference customers:
|
||||
|
||||
- `Customer` stores customer data (email, name, addresses, preferences)
|
||||
- `CustomerService` handles CRUD, authentication, and profile management
|
||||
- Customers module has **no knowledge** of orders, loyalty points, or any specific consumers
|
||||
|
||||
### 2. Consumer-Owned Relationships
|
||||
|
||||
Each module that references customers defines its **own relationship**:
|
||||
|
||||
- **Orders**: `Order.customer_id` links orders to customers
|
||||
- **Future Loyalty**: Would define `LoyaltyPoints.customer_id`
|
||||
- **Future Subscriptions**: Would define `Subscription.customer_id`
|
||||
|
||||
This follows the principle: **The consumer knows what it needs, the provider doesn't need to know who uses it.**
|
||||
|
||||
### 3. Correct Dependency Direction
|
||||
|
||||
```
|
||||
WRONG (Hidden Dependency):
|
||||
Customers → Orders (customers imports Order model)
|
||||
|
||||
CORRECT:
|
||||
Orders → Customers (orders references Customer via FK)
|
||||
```
|
||||
|
||||
Optional modules (orders) depend on core modules (customers), never the reverse.
|
||||
|
||||
## Key Components
|
||||
|
||||
### Customer Model (Customers Module)
|
||||
|
||||
```python
|
||||
# app/modules/customers/models/customer.py
|
||||
|
||||
class Customer(Base, TimestampMixin):
|
||||
"""Generic customer - consumer-agnostic."""
|
||||
|
||||
__tablename__ = "customers"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
store_id = Column(Integer, ForeignKey("stores.id"), nullable=False)
|
||||
|
||||
# Authentication
|
||||
email = Column(String(255), nullable=False)
|
||||
hashed_password = Column(String(255))
|
||||
|
||||
# Profile
|
||||
first_name = Column(String(100))
|
||||
last_name = Column(String(100))
|
||||
phone = Column(String(50))
|
||||
customer_number = Column(String(50), unique=True)
|
||||
|
||||
# Preferences
|
||||
marketing_consent = Column(Boolean, default=False)
|
||||
preferred_language = Column(String(10))
|
||||
|
||||
# Status
|
||||
is_active = Column(Boolean, default=True)
|
||||
|
||||
# Note: Consumer-specific relationships (orders, loyalty points, etc.)
|
||||
# are defined in their respective modules. Customers module doesn't
|
||||
# know about specific consumers.
|
||||
```
|
||||
|
||||
### CustomerService (Customers Module)
|
||||
|
||||
The `CustomerService` provides generic operations:
|
||||
|
||||
```python
|
||||
# app/modules/customers/services/customer_service.py
|
||||
|
||||
class CustomerService:
|
||||
"""Generic customer operations - consumer-agnostic."""
|
||||
|
||||
def create_customer(self, db, store_id, customer_data):
|
||||
"""Create a new customer."""
|
||||
...
|
||||
|
||||
def get_customer(self, db, store_id, customer_id):
|
||||
"""Get a customer by ID."""
|
||||
...
|
||||
|
||||
def update_customer(self, db, store_id, customer_id, customer_data):
|
||||
"""Update customer profile."""
|
||||
...
|
||||
|
||||
def login_customer(self, db, store_id, email, password):
|
||||
"""Authenticate a customer."""
|
||||
...
|
||||
|
||||
# Note: Customer order methods have been moved to the orders module.
|
||||
# Use orders.services.customer_order_service for order-related operations.
|
||||
```
|
||||
|
||||
### Order Model (Orders Module)
|
||||
|
||||
```python
|
||||
# app/modules/orders/models/order.py
|
||||
|
||||
class Order(Base, TimestampMixin):
|
||||
"""Order with customer reference - orders owns the relationship."""
|
||||
|
||||
__tablename__ = "orders"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
store_id = Column(Integer, ForeignKey("stores.id"), nullable=False)
|
||||
|
||||
# Customer reference - orders module owns this relationship
|
||||
customer_id = Column(Integer, ForeignKey("customers.id"))
|
||||
|
||||
# Order data
|
||||
order_number = Column(String(50), unique=True)
|
||||
status = Column(String(20), default="pending")
|
||||
total_cents = Column(Integer)
|
||||
...
|
||||
|
||||
# Relationship to customer
|
||||
customer = relationship("Customer", lazy="joined")
|
||||
```
|
||||
|
||||
### CustomerOrderService (Orders Module)
|
||||
|
||||
```python
|
||||
# app/modules/orders/services/customer_order_service.py
|
||||
|
||||
class CustomerOrderService:
|
||||
"""Customer-order operations - owned by orders module."""
|
||||
|
||||
def get_customer_orders(self, db, store_id, customer_id, skip=0, limit=50):
|
||||
"""Get orders for a specific customer."""
|
||||
...
|
||||
|
||||
def get_recent_orders(self, db, store_id, customer_id, limit=5):
|
||||
"""Get recent orders for a customer."""
|
||||
...
|
||||
|
||||
def get_order_count(self, db, store_id, customer_id):
|
||||
"""Get total order count for a customer."""
|
||||
...
|
||||
```
|
||||
|
||||
### Customer Order Metrics (Orders Module)
|
||||
|
||||
Order statistics for customers use the MetricsProvider pattern:
|
||||
|
||||
```python
|
||||
# app/modules/orders/services/order_metrics.py
|
||||
|
||||
class OrderMetricsProvider:
|
||||
"""Metrics provider including customer-level order metrics."""
|
||||
|
||||
def get_customer_order_metrics(self, db, store_id, customer_id, context=None):
|
||||
"""
|
||||
Get order metrics for a specific customer.
|
||||
|
||||
Returns MetricValue objects for:
|
||||
- total_orders: Total orders placed
|
||||
- total_spent: Total amount spent
|
||||
- avg_order_value: Average order value
|
||||
- last_order_date: Date of most recent order
|
||||
- first_order_date: Date of first order
|
||||
"""
|
||||
...
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Customers Module Endpoints
|
||||
|
||||
Customer CRUD operations (no order data):
|
||||
|
||||
```
|
||||
GET /api/store/customers → List customers
|
||||
GET /api/store/customers/{id} → Customer details (no order stats)
|
||||
PUT /api/store/customers/{id} → Update customer
|
||||
PUT /api/store/customers/{id}/status → Toggle active status
|
||||
```
|
||||
|
||||
### Orders Module Endpoints
|
||||
|
||||
Customer order data (owned by orders):
|
||||
|
||||
```
|
||||
GET /api/store/customers/{id}/orders → Customer's order history
|
||||
GET /api/store/customers/{id}/order-stats → Customer's order statistics
|
||||
```
|
||||
|
||||
## Adding Customer References to a New Module
|
||||
|
||||
When creating a module that references customers (e.g., a loyalty module):
|
||||
|
||||
### Step 1: Reference Customer via Foreign Key
|
||||
|
||||
```python
|
||||
# app/modules/loyalty/models/loyalty_points.py
|
||||
|
||||
class LoyaltyPoints(Base):
|
||||
"""Loyalty points - owned by loyalty module."""
|
||||
|
||||
__tablename__ = "loyalty_points"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
store_id = Column(Integer, ForeignKey("stores.id"))
|
||||
|
||||
# Reference to customer - loyalty module owns this
|
||||
customer_id = Column(Integer, ForeignKey("customers.id"))
|
||||
|
||||
points_balance = Column(Integer, default=0)
|
||||
tier = Column(String(20), default="bronze")
|
||||
|
||||
# Relationship
|
||||
customer = relationship("Customer", lazy="joined")
|
||||
```
|
||||
|
||||
### Step 2: Create Your Service
|
||||
|
||||
```python
|
||||
# app/modules/loyalty/services/customer_loyalty_service.py
|
||||
|
||||
class CustomerLoyaltyService:
|
||||
"""Customer loyalty operations - owned by loyalty module."""
|
||||
|
||||
def get_customer_points(self, db, store_id, customer_id):
|
||||
"""Get loyalty points for a customer."""
|
||||
...
|
||||
|
||||
def add_points(self, db, store_id, customer_id, points, reason):
|
||||
"""Add points to customer's balance."""
|
||||
...
|
||||
```
|
||||
|
||||
### Step 3: Add Routes in Your Module
|
||||
|
||||
```python
|
||||
# app/modules/loyalty/routes/api/store.py
|
||||
|
||||
@router.get("/customers/{customer_id}/loyalty")
|
||||
def get_customer_loyalty(customer_id: int, ...):
|
||||
"""Get loyalty information for a customer."""
|
||||
return loyalty_service.get_customer_points(db, store_id, customer_id)
|
||||
```
|
||||
|
||||
## Benefits of This Architecture
|
||||
|
||||
1. **Module Independence**: Orders can be disabled without affecting customers
|
||||
2. **Extensibility**: New modules easily reference customers
|
||||
3. **No Hidden Dependencies**: Dependencies flow in one direction
|
||||
4. **Clean Separation**: Customers handles identity, consumers handle their domain
|
||||
5. **Testability**: Can test customers without any consumer modules
|
||||
6. **Single Responsibility**: Each module owns its domain
|
||||
|
||||
## Anti-Patterns to Avoid
|
||||
|
||||
### Don't: Import Consumer Models in Customers
|
||||
|
||||
```python
|
||||
# BAD - Creates hidden dependency
|
||||
class CustomerService:
|
||||
def get_customer_orders(self, db, customer_id):
|
||||
from app.modules.orders.models import Order # Wrong!
|
||||
return db.query(Order).filter(Order.customer_id == customer_id).all()
|
||||
```
|
||||
|
||||
### Don't: Add Consumer-Specific Fields to Customer
|
||||
|
||||
```python
|
||||
# BAD - Customer shouldn't know about orders
|
||||
class Customer(Base):
|
||||
# These create coupling to orders module
|
||||
total_orders = Column(Integer) # Wrong approach
|
||||
last_order_date = Column(DateTime) # Wrong approach
|
||||
```
|
||||
|
||||
Instead, query order data from the orders module when needed.
|
||||
|
||||
### Don't: Put Consumer Routes in Customers Module
|
||||
|
||||
```python
|
||||
# BAD - customers/routes shouldn't serve order data
|
||||
@router.get("/customers/{id}/orders")
|
||||
def get_customer_orders(customer_id: int):
|
||||
from app.modules.orders.models import Order # Wrong!
|
||||
...
|
||||
```
|
||||
|
||||
## Migration Note
|
||||
|
||||
Previously, the customers module had methods that imported from orders:
|
||||
|
||||
```python
|
||||
# OLD (removed)
|
||||
class CustomerService:
|
||||
def get_customer_orders(self, db, store_id, customer_id):
|
||||
from app.modules.orders.models import Order # Lazy import
|
||||
...
|
||||
|
||||
def get_customer_statistics(self, db, store_id, customer_id):
|
||||
from app.modules.orders.models import Order # Lazy import
|
||||
...
|
||||
```
|
||||
|
||||
These have been moved to the orders module:
|
||||
- `get_customer_orders()` → `orders.services.customer_order_service`
|
||||
- `get_customer_statistics()` → `orders.services.order_metrics.get_customer_order_metrics()`
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Media Architecture](../../architecture/media-architecture.md) - Similar pattern for media files
|
||||
- [Module System Architecture](../../architecture/module-system.md) - Module structure and dependencies
|
||||
- [Cross-Module Import Rules](../../architecture/cross-module-import-rules.md) - Import restrictions
|
||||
- [Metrics Provider Pattern](../../architecture/metrics-provider-pattern.md) - Provider pattern for statistics
|
||||
Reference in New Issue
Block a user