Skip to content

Architecture Overview

High-Level Architecture

The SaaS-Courier system follows a Modular Monolith architecture with Clean Architecture principles within each module.

┌─────────────────────────────────────────────────────────────┐
│                        Presentation                         │
│   (FastAPI Routes, Schemas, WebSocket, Dependencies)       │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│                        Application                          │
│   (DTOs, Use Cases, Application Services, Protocols)       │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│                          Domain                             │
│   (Entities, Value Objects, Repository Interfaces, Events)  │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│                      Infrastructure                         │
│   (SQLModel Models, Repository Impl, External Services)       │
└─────────────────────────────────────────────────────────────┘

Backend Module Structure

Each module follows Clean Architecture:

modules/<module>/
├── domain/                    # Pure Python - NO framework dependencies
│   ├── entities.py           # Domain entities (frozen dataclasses)
│   ├── repositories.py        # Repository interfaces (Protocols)
│   └── exceptions.py         # Domain exceptions
├── application/               # Use case orchestration
│   ├── dtos.py              # Data Transfer Objects
│   ├── interfaces/          # Protocol interfaces (Dependency Rule)
│   └── usecases/            # Use case implementations
├── infrastructure/            # External implementations
│   ├── persistence/         # SQLModel models, repository implementations
│   ├── services/            # JWT, password, email service implementations
│   └── adapters/            # Third-party service adapters
└── presentation/             # FastAPI layer
    ├── routes/              # API endpoints
    ├── schemas/             # Pydantic/SQLModel schemas
    └── dependencies.py     # FastAPI dependencies (DI)

Frontend Module Structure

saas-fronted/
├── core/                        # Infrastructure (no business logic)
│   ├── config.py               # Environment configuration
│   ├── error/                  # Result type, failures
│   ├── di/                      # Service locator
│   ├── network/                 # ApiClient (httpx async)
│   ├── router/                  # AppRouter
│   ├── security/                # Rate limiter, input validation, IP blocklist
│   ├── storage/                 # SharedPreferences
│   └── theme/                   # Theme system (tokens + builder)
├── modules/                     # Feature modules
│   └── <module>/
│       ├── domain/             # Entities, repositories (abstract), usecases
│       ├── data/               # Datasources, repository impl
│       └── presentation/        # Controllers, pages, components
└── shared/                      # Reusable UI components

Key Design Decisions

1. SQLModel for ORM and Validation

SQLModel unifies SQLAlchemy ORM with Pydantic validation:

class UserModel(SQLModel, table=True):
    id: UUID = Field(primary_key=True)
    email: str = Field(unique=True, max_length=255)

2. Hybrid Entity Pattern

Domain entities use frozen dataclasses with with_* factory methods for state transitions:

@dataclass(frozen=True)
class User:
    id: UUID
    email: str

    @staticmethod
    def create(email: str) -> "User":
        return User(id=uuid4(), email=email)

    def with_email(self, new_email: str) -> "User":
        return dataclasses.replace(self, email=new_email)

3. Protocol-Based Dependency Rule

Use cases depend on Protocol interfaces instead of concrete implementations:

class JWTServiceProtocol(Protocol):
    def create_access_token(self, data: dict) -> str: ...

class LoginUseCase:
    def __init__(self, jwt_service: JWTServiceProtocol, ...):
        self.jwt_service = jwt_service

4. Multi-Tenancy via RLS

PostgreSQL Row Level Security isolates tenant data with context manager.

5. API Versioning

URL-based versioning allows different response formats:

/api/v1/shipments  → v1 response format
/api/v2/shipments  → v2 response format (additive fields)

Dependency Rule

Presentation → Application → Domain ← Infrastructure
  • Domain: Never imports from other layers (pure Python)
  • Application: Uses interfaces from Domain (Protocols)
  • Infrastructure: Implements Domain interfaces
  • Presentation: Orchestrates via Application use cases