Cybersecurity-Projects/PROJECTS/advanced/bug-bounty-platform/learn/ARCHITECTURE.md

1070 lines
38 KiB
Markdown

# Architecture Deep Dive
This document provides an in-depth explanation of the Bug Bounty Platform's architecture, design decisions, and the reasoning behind technical choices.
---
## Table of Contents
1. [Overview](#overview)
2. [High-Level Architecture](#high-level-architecture)
3. [Backend Architecture](#backend-architecture)
4. [Frontend Architecture](#frontend-architecture)
5. [Database Design](#database-design)
6. [Security Architecture](#security-architecture)
7. [Infrastructure](#infrastructure)
8. [Design Decisions](#design-decisions)
---
## Overview
This platform is built using a modern, production-ready architecture that emphasizes:
1. **Separation of Concerns** - Clear boundaries between layers
2. **Type Safety** - Compile-time guarantees in both backend and frontend
3. **Async-First** - Scalable concurrent operations
4. **Security by Design** - Multiple defense layers
5. **Developer Experience** - Hot reload, linting, type checking
6. **Production Ready** - Containerized, migrated, monitored
---
## High-Level Architecture
```
┌─────────────────────────────────────────────────────────────┐
│ Client Browser │
│ (React + TypeScript SPA) │
└────────────────────────┬────────────────────────────────────┘
│ HTTPS
┌─────────────────────────────────────────────────────────────┐
│ Nginx (Reverse Proxy) │
│ - Serves static React build │
│ - Proxies /api/* to FastAPI backend │
│ - Gzip compression + security headers │
└────────────────────────┬────────────────────────────────────┘
┌────────────────┴────────────────┐
│ │
▼ ▼
┌────────────────────┐ ┌────────────────────┐
│ FastAPI Backend │◄────────►│ Redis (Cache) │
│ (Python 3.12+) │ │ - Sessions │
│ - REST API │ │ - Rate limiting │
│ - JWT Auth │ └────────────────────┘
│ - Business Logic │
└─────────┬──────────┘
┌────────────────────┐
│ PostgreSQL 18 │
│ - Primary data │
│ - UUID v7 PKs │
│ - ACID guarantees │
└────────────────────┘
```
### Why This Architecture?
**Traditional Monolith vs. Microservices:**
- This is a **modular monolith** - easier to develop, deploy, and debug than microservices
- Services can be extracted into microservices later if needed
- Single database = ACID transactions across all entities
**Why Nginx?**
- Production-grade reverse proxy
- Static file serving for React build
- Gzip compression reduces bandwidth
- SSL/TLS termination
- Load balancing (if scaled horizontally)
**Why Redis?**
- Fast in-memory cache for session data
- Rate limiting without hitting PostgreSQL
- Can be used for pub/sub (WebSockets) if needed later
---
## Backend Architecture
### Layered Architecture
The backend follows a **strict layered architecture**:
```
┌──────────────────────────────────────────────────────────┐
│ Routes (API Layer) │
│ - HTTP request/response handling │
│ - Input validation (Pydantic schemas) │
│ - Dependency injection │
│ - OpenAPI documentation │
└────────────────────────┬─────────────────────────────────┘
│ calls
┌──────────────────────────────────────────────────────────┐
│ Services (Business Logic) │
│ - Core business rules │
│ - Orchestrates multiple repositories │
│ - Transaction management │
│ - Domain logic │
└────────────────────────┬─────────────────────────────────┘
│ calls
┌──────────────────────────────────────────────────────────┐
│ Repositories (Data Access) │
│ - CRUD operations │
│ - Query building │
│ - Database interaction │
│ - No business logic │
└────────────────────────┬─────────────────────────────────┘
│ operates on
┌──────────────────────────────────────────────────────────┐
│ Models (Database Entities) │
│ - SQLAlchemy ORM models │
│ - Relationships │
│ - Database schema definition │
└──────────────────────────────────────────────────────────┘
```
### Example: User Login Flow
Let's trace a login request through the layers:
```python
# 1. Route Layer (backend/app/auth/routes.py)
@router.post("/login")
async def login(
credentials: LoginRequest,
session: DatabaseSession,
) -> TokenResponse:
access_token, refresh_token = await authenticate_user(
session=session,
email=credentials.email,
password=credentials.password,
device_info=credentials.device_info,
)
return TokenResponse(
access_token=access_token,
refresh_token=refresh_token,
)
```
The route handler:
- Receives HTTP POST request at `/api/v1/auth/login`
- Validates input using `LoginRequest` Pydantic model
- Injects database session via FastAPI's `Depends()`
- Calls service function `authenticate_user()`
- Returns response as `TokenResponse` Pydantic model
```python
# 2. Service Layer (backend/app/auth/service.py)
async def authenticate_user(
session: AsyncSession,
email: str,
password: str,
device_info: str | None,
) -> tuple[str, str]:
user_repo = UserRepository(session)
# Find user by email
user = await user_repo.find_by_email(email)
if not user:
raise InvalidCredentialsError()
# Verify password (timing-safe comparison)
if not security.verify_password(password, user.password_hash):
raise InvalidCredentialsError()
# Create tokens
access_token = security.create_access_token(user)
refresh_token = security.create_refresh_token()
# Store refresh token in database
token_repo = RefreshTokenRepository(session)
await token_repo.create_refresh_token(
user_id=user.id,
token=refresh_token,
device_info=device_info,
)
await session.commit()
return access_token, refresh_token
```
The service layer:
- Implements business logic (authentication rules)
- Coordinates multiple repositories (User + RefreshToken)
- Handles password verification securely
- Creates JWT tokens
- Manages transactions (commit)
- Throws domain exceptions (`InvalidCredentialsError`)
```python
# 3. Repository Layer (backend/app/user/repository.py)
class UserRepository(BaseRepository[User]):
async def find_by_email(self, email: str) -> User | None:
stmt = select(User).where(User.email == email)
result = await self.session.execute(stmt)
return result.scalar_one_or_none()
```
The repository:
- Builds SQL query using SQLAlchemy
- Executes query against database
- Returns ORM model or None
- No business logic - pure data access
```python
# 4. Model Layer (backend/app/user/models.py)
class User(Base):
__tablename__ = "users"
id: Mapped[UUID] = mapped_column(UUID(as_uuid=True), primary_key=True)
email: Mapped[str] = mapped_column(String(255), unique=True, index=True)
password_hash: Mapped[str] = mapped_column(String(255))
full_name: Mapped[str] = mapped_column(String(255))
role: Mapped[UserRole] = mapped_column(Enum(UserRole))
# Relationships
refresh_tokens: Mapped[list["RefreshToken"]] = relationship(
back_populates="user",
cascade="all, delete-orphan",
)
```
The model:
- Defines database schema
- Maps Python classes to database tables
- Declares relationships between entities
- Provides type hints for all fields
### Why Layers?
**Separation of Concerns:**
- Routes don't know about database queries
- Services don't know about HTTP
- Repositories don't implement business rules
- Models are pure data structures
**Testability:**
- Mock repositories to test services
- Mock services to test routes
- Unit test each layer independently
**Maintainability:**
- Change database? Update repositories only
- Change business logic? Update services only
- Change API format? Update routes and schemas only
**Type Safety:**
- Each layer has strict type annotations
- MyPy verifies types at compile time
- Refactoring is safe and predictable
---
## Module Structure
### Domain-Driven Design (DDD)
Each domain module is self-contained:
```
backend/src/app/user/
├── __init__.py
├── models.py # User database model
├── repository.py # UserRepository
├── schemas.py # Pydantic request/response models
├── routes.py # API endpoints
├── service.py # Business logic functions
└── exceptions.py # Domain-specific exceptions
```
**Why this structure?**
- All user-related code is in one place
- Easy to find relevant files
- Can be extracted to a separate service later
- Clear ownership and boundaries
### Core Module
```
backend/src/app/core/
├── Base.py # Base model classes (UUIDMixin, etc.)
├── base_repository.py # Generic repository with CRUD
├── database.py # Database session management
├── security.py # Password hashing, JWT creation
├── dependencies.py # FastAPI dependencies (auth, etc.)
├── exceptions.py # Base exception classes
├── enums.py # SafeEnum pattern
├── logging.py # Structured logging
├── rate_limit.py # Rate limiting configuration
└── constants.py # String length constraints
```
The core module provides:
- Reusable base classes
- Shared utilities
- Database connection management
- Security primitives
---
## Frontend Architecture
### Component-Based Architecture
React's component model enables:
- **Reusability:** UI elements as self-contained components
- **Composability:** Complex UIs from simple pieces
- **Maintainability:** Change one component without breaking others
```
frontend/src/
├── routes/ # File-based routing
│ ├── landing/ # Public landing page
│ ├── login/ # Authentication
│ ├── dashboard/ # User dashboard
│ ├── programs/ # Browse programs
│ │ └── [slug]/ # Dynamic route for program detail
│ ├── company/ # Company dashboard (nested routes)
│ │ ├── programs/ # Manage programs
│ │ ├── inbox/ # Incoming reports
│ │ └── reports/ # Report triage
│ └── admin/ # Admin panel
├── components/ # Reusable UI components
│ ├── common/ # Shared components (Button, Card, etc.)
│ ├── forms/ # Form components
│ └── layouts/ # Layout components (Shell, etc.)
├── api/ # Backend integration
│ ├── hooks/ # React Query hooks (useAuth, usePrograms)
│ ├── types/ # TypeScript interfaces
│ └── index.ts # Axios client configuration
├── stores/ # State management (Zustand)
│ ├── auth.store.ts # Authentication state
│ ├── shell.ui.store.ts # UI state (sidebar, modals)
│ └── *.form.store.ts # Form state
└── core/ # App configuration
├── app/ # App setup (router, providers)
├── styles/ # Global styles
└── config.ts # Constants and configuration
```
### State Management Strategy
The frontend uses a **hybrid state management approach**:
```
┌─────────────────────────────────────────────────────────┐
│ Server State (TanStack Query) │
│ - API data (users, programs, reports) │
│ - Automatic caching and revalidation │
│ - Loading/error states │
│ - Optimistic updates │
└─────────────────────────────────────────────────────────┘
+
┌─────────────────────────────────────────────────────────┐
│ Client State (Zustand) │
│ - UI state (sidebar open/closed, modals) │
│ - Form state (program creation, report submission) │
│ - Authentication tokens (persisted to localStorage) │
└─────────────────────────────────────────────────────────┘
```
**Why two state management libraries?**
1. **TanStack Query for server state:**
- Automatic caching (no need to manually cache API responses)
- Background refetching (keeps data fresh)
- Loading and error states out of the box
- Optimistic updates for better UX
- Pagination and infinite scroll support
2. **Zustand for client state:**
- Simple API (less boilerplate than Redux)
- TypeScript-first design
- Persistence support (auth tokens)
- No provider wrapper needed
- Minimal re-renders
**Example: Fetching programs with TanStack Query**
```typescript
// api/hooks/usePrograms.ts
export const usePrograms = (params?: ProgramQueryParams) => {
return useQuery({
queryKey: ["programs", params],
queryFn: () => api.get<ProgramListResponse>("/programs", { params }),
staleTime: 5 * 60 * 1000, // 5 minutes
});
};
// In component
function ProgramList() {
const { data, isLoading, error } = usePrograms({ page: 1, limit: 20 });
if (isLoading) return <LoadingSpinner />;
if (error) return <ErrorMessage error={error} />;
return (
<div>
{data.items.map(program => (
<ProgramCard key={program.id} program={program} />
))}
</div>
);
}
```
TanStack Query automatically:
- Caches the response (keyed by `["programs", params]`)
- Shows loading state while fetching
- Refetches on window focus (configurable)
- Deduplicates concurrent requests
- Provides error handling
**Example: UI state with Zustand**
```typescript
// stores/shell.ui.store.ts
interface ShellUIStore {
sidebarOpen: boolean;
toggleSidebar: () => void;
}
export const useShellUIStore = create<ShellUIStore>((set) => ({
sidebarOpen: true,
toggleSidebar: () => set((state) => ({
sidebarOpen: !state.sidebarOpen
})),
}));
// In component
function Sidebar() {
const { sidebarOpen, toggleSidebar } = useShellUIStore();
return (
<aside className={sidebarOpen ? "open" : "closed"}>
<button onClick={toggleSidebar}>Toggle</button>
</aside>
);
}
```
Zustand provides:
- Simple hook-based API
- No provider wrapper
- TypeScript support
- Minimal re-renders (only components using the changed state)
### File-Based Routing
Using React Router 7's file-based routing:
```
routes/
├── landing/
│ └── page.tsx # → /
├── login/
│ └── page.tsx # → /login
├── programs/
│ ├── page.tsx # → /programs
│ └── [slug]/
│ └── page.tsx # → /programs/:slug
└── company/
├── layout.tsx # Shared layout for /company/*
├── programs/
│ ├── page.tsx # → /company/programs
│ └── [id]/
│ └── page.tsx # → /company/programs/:id
└── inbox/
└── page.tsx # → /company/inbox
```
**Benefits:**
- Route structure mirrors file structure
- Automatic code splitting (each route is a separate chunk)
- Nested layouts (company routes share a layout)
- Dynamic routes with `[param]` syntax
---
## Database Design
### Schema Overview
```
┌──────────────┐
│ users │
│──────────────│
│ id (UUID v7) │◄───┐
│ email │ │
│ password │ │
│ role │ │
└──────────────┘ │
┌─────────────┴──────────────┬────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌─────────────┐ ┌────────────┐
│refresh_tokens │ │ programs │ │ reports │
│───────────────│ │─────────────│ │────────────│
│ id │ │ id │ │ id │
│ user_id (FK) │ │ owner_id FK │ │ author_id │
│ token_hash │ │ name │ │ program_id │
│ device_info │ │ slug │ │ title │
│ family_id │ │ status │ │ severity │
└───────────────┘ └─────────────┘ └────────────┘
┌───────────┴───────────┐
▼ ▼
┌──────────┐ ┌──────────────┐
│ assets │ │ reward_tiers │
│──────────│ │──────────────│
│ id │ │ id │
│ program │ │ program_id │
│ type │ │ severity │
│ target │ │ amount │
└──────────┘ └──────────────┘
```
### UUID v7 Primary Keys
Traditional auto-increment IDs have problems:
- Predictable (security risk - enumerate all records)
- Not globally unique (can't merge databases)
- Require database round-trip to generate
UUIDs solve this but have their own issues:
- UUID v4 is random (bad for database indexing)
- Not time-sortable (can't ORDER BY id to get chronological order)
**UUID v7 is the best of both worlds:**
- Time-sortable (first 48 bits are Unix timestamp in milliseconds)
- Globally unique (no collisions even across databases)
- Good for database indexes (lexicographic order = chronological order)
- Secure (remaining bits are random)
```python
import uuid_utils as uuid
# Generate UUID v7
user_id = uuid.uuid7() # → 018d3f54-8c3a-7000-a234-56789abcdef0
# ^^^^^^^^^^^^^^^^ ← timestamp
# ^^^^^^^^^^^^^^^^^^^ ← random
```
### SafeEnum Pattern
Traditional enums in SQLAlchemy store the enum name:
```python
class Status(enum.Enum):
ACTIVE = "active"
PAUSED = "paused"
# Database stores: "ACTIVE" (the Python name)
```
**Problem:** If you rename the Python enum, the database breaks:
```python
class Status(enum.Enum):
RUNNING = "active" # Renamed ACTIVE → RUNNING
PAUSED = "paused"
# Database still has "ACTIVE", but Python doesn't recognize it!
```
**SafeEnum solution:** Store the value, not the name:
```python
class Status(SafeEnum):
ACTIVE = "active"
PAUSED = "paused"
# Database stores: "active" (the value)
```
Now you can safely rename:
```python
class Status(SafeEnum):
RUNNING = "active" # Value is still "active"
PAUSED = "paused"
# Database has "active", Python maps it to Status.RUNNING ✓
```
### Soft Deletes vs Hard Deletes
Some models use **soft deletes** (set `deleted_at` timestamp instead of removing row):
```python
class SoftDeleteMixin:
deleted_at: Mapped[datetime | None] = mapped_column(
DateTime(timezone=True),
nullable=True,
default=None,
)
```
**When to use soft deletes:**
- User accounts (compliance requirements - keep audit trail)
- Financial records (never truly delete)
- Reports (preserve history even if program is deleted)
**When to use hard deletes:**
- Session tokens (no need to keep after logout)
- Temporary data (caches, OTPs)
- GDPR deletion requests (must truly delete)
---
## Security Architecture
### Multi-Layer Defense
Security is implemented at multiple layers:
```
┌──────────────────────────────────────────────────────┐
│ 1. Input Validation (Pydantic) │
│ - Type checking │
│ - String length limits │
│ - Email/URL format validation │
└────────────────────────┬─────────────────────────────┘
┌────────────────────────▼─────────────────────────────┐
│ 2. Authentication (JWT) │
│ - Token-based auth │
│ - Short-lived access tokens (15 min) │
│ - Long-lived refresh tokens (7 days) │
│ - Token versioning │
└────────────────────────┬─────────────────────────────┘
┌────────────────────────▼─────────────────────────────┐
│ 3. Authorization (RBAC) │
│ - Role-based access control │
│ - Resource ownership checks │
│ - Admin-only endpoints │
└────────────────────────┬─────────────────────────────┘
┌────────────────────────▼─────────────────────────────┐
│ 4. Rate Limiting │
│ - 100 req/min default │
│ - 20 req/min for auth endpoints │
│ - Per-IP tracking │
└────────────────────────┬─────────────────────────────┘
┌────────────────────────▼─────────────────────────────┐
│ 5. Secure Storage │
│ - Argon2id password hashing │
│ - Hashed refresh tokens (not plaintext) │
│ - Encrypted secrets in env vars │
└──────────────────────────────────────────────────────┘
```
### JWT Token Flow
```
User Login
┌──────────────────────────────────────┐
│ 1. POST /api/v1/auth/login │
│ { email, password } │
└────────────┬─────────────────────────┘
┌──────────────────────────────────────┐
│ 2. Verify password (Argon2id) │
│ - Timing-safe comparison │
└────────────┬─────────────────────────┘
┌──────────────────────────────────────┐
│ 3. Generate tokens │
│ - access_token (15 min) │
│ - refresh_token (7 days) │
└────────────┬─────────────────────────┘
┌──────────────────────────────────────┐
│ 4. Store refresh token in DB │
│ - Hashed (not plaintext) │
│ - With device info and IP │
│ - Family ID for replay detection │
└────────────┬─────────────────────────┘
┌──────────────────────────────────────┐
│ 5. Return tokens to client │
│ { access_token, refresh_token } │
└──────────────────────────────────────┘
```
**Access token payload:**
```json
{
"sub": "018d3f54-8c3a-7000-a234-56789abcdef0", // user_id
"role": "USER",
"token_version": 1,
"exp": 1704123456, // expires in 15 minutes
"iat": 1704122556
}
```
**Why short-lived access tokens?**
- If stolen, attacker only has 15 minutes
- No way to revoke access tokens (they're stateless)
- Must use refresh token to get new access token
**Refresh token flow:**
```
Access token expires (15 min)
┌──────────────────────────────────────┐
│ 1. POST /api/v1/auth/refresh │
│ { refresh_token } │
└────────────┬─────────────────────────┘
┌──────────────────────────────────────┐
│ 2. Verify refresh token in DB │
│ - Check hash matches │
│ - Check not expired │
│ - Check not revoked │
└────────────┬─────────────────────────┘
┌──────────────────────────────────────┐
│ 3. Token rotation │
│ - Delete old refresh token │
│ - Generate new refresh token │
│ - Store new token in DB │
└────────────┬─────────────────────────┘
┌──────────────────────────────────────┐
│ 4. Generate new access token │
│ - Same user_id │
│ - Incremented token_version │
└────────────┬─────────────────────────┘
┌──────────────────────────────────────┐
│ 5. Return new tokens │
│ { access_token, refresh_token } │
└──────────────────────────────────────┘
```
**Token rotation prevents replay attacks:**
- Each refresh token is single-use
- If an attacker steals a refresh token, it becomes invalid after one use
- If a refresh token is reused, we detect it (family ID mismatch) and revoke all tokens for that user
### Token Versioning
Token versioning allows instant invalidation:
```python
class User(Base):
token_version: Mapped[int] = mapped_column(Integer, default=1)
# When user changes password:
user.token_version += 1
await session.commit()
```
Now all existing access tokens become invalid:
- Old tokens have `token_version: 1`
- User's current `token_version` is `2`
- Token verification fails: `1 != 2`
No need to maintain a token blacklist!
---
## Infrastructure
### Docker Compose Architecture
**Production (`compose.yml`):**
```yaml
services:
nginx:
build: ./infra/nginx
ports:
- "${NGINX_HOST_PORT}:80"
depends_on:
- backend
backend:
build: ./backend
environment:
- DATABASE_URL=${DATABASE_URL}
- REDIS_URL=${REDIS_URL}
- SECRET_KEY=${SECRET_KEY}
depends_on:
- db
- redis
db:
image: postgres:18-alpine
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=${POSTGRES_DB}
redis:
image: redis:7-alpine
volumes:
- redis_data:/data
```
**Why this structure?**
- **nginx depends on backend:** Nginx can't start until backend is ready
- **backend depends on db + redis:** Backend needs database and cache
- **Volumes for data persistence:** Database and Redis data survives container restarts
### Multi-Stage Docker Builds
**Backend Dockerfile:**
```dockerfile
# Stage 1: Build dependencies
FROM python:3.12-slim as builder
WORKDIR /app
COPY pyproject.toml .
RUN pip install --no-cache-dir -e .
# Stage 2: Production image
FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
COPY ./src ./src
CMD ["gunicorn", "main:app"]
```
**Benefits:**
- Smaller final image (no build tools)
- Faster builds (dependencies cached in builder stage)
- More secure (no build dependencies in production)
**Frontend Dockerfile (Nginx):**
```dockerfile
# Stage 1: Build React app
FROM node:22-alpine as builder
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN pnpm install
COPY . .
RUN pnpm build
# Stage 2: Serve with Nginx
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf
```
**Benefits:**
- No Node.js in production image
- Nginx is optimized for serving static files
- Smaller final image (~50MB vs ~500MB)
---
## Design Decisions
### Why FastAPI?
**Compared to Flask:**
- Async/await support (FastAPI wins)
- Automatic OpenAPI docs (FastAPI wins)
- Data validation built-in with Pydantic (FastAPI wins)
- Type hints for IDE autocomplete (FastAPI wins)
**Compared to Django:**
- Async ORM support (tie - Django 4.1+ has it)
- Flexibility (FastAPI wins - Django is opinionated)
- Admin panel (Django wins)
- Batteries included (Django wins - has auth, admin, etc.)
**Decision:** FastAPI for its async-first design and modern Python features.
### Why PostgreSQL?
**Compared to MySQL:**
- JSON support (tie)
- Full-text search (PostgreSQL wins)
- ACID compliance (tie)
- JSON indexes (PostgreSQL wins)
**Compared to MongoDB:**
- ACID transactions (PostgreSQL wins)
- Schema validation (tie - Postgres has JSON schema)
- Joins (PostgreSQL wins)
- Flexibility (MongoDB wins for unstructured data)
**Decision:** PostgreSQL for ACID guarantees and relational data modeling.
### Why React + TypeScript?
**Compared to Vue:**
- Ecosystem size (React wins)
- TypeScript support (tie)
- Learning curve (Vue wins - easier)
- Corporate backing (tie - React by Meta, Vue independent)
**Compared to Svelte:**
- Maturity (React wins)
- Job market (React wins)
- Bundle size (Svelte wins)
- Learning curve (Svelte wins)
**Decision:** React + TypeScript for its mature ecosystem and industry adoption.
### Why Zustand?
**Compared to Redux:**
- Boilerplate (Zustand wins - much simpler)
- DevTools (Redux wins - more mature)
- Middleware (tie)
- Bundle size (Zustand wins)
**Compared to Context API:**
- Performance (Zustand wins - no unnecessary re-renders)
- Persistence (Zustand wins - built-in)
- DX (Zustand wins - simpler API)
**Decision:** Zustand for its simplicity and performance.
### Why TanStack Query?
**Compared to Redux Toolkit Query:**
- Flexibility (TanStack wins - not tied to Redux)
- Cache management (tie)
- Bundle size (TanStack wins)
- Learning curve (tie)
**Compared to SWR:**
- Features (TanStack wins - more complete)
- Bundle size (SWR wins)
- Pagination (TanStack wins)
- Community (tie)
**Decision:** TanStack Query for its comprehensive feature set and framework-agnostic design.
---
## Performance Considerations
### Database Indexing
Indexes are created for frequently queried columns:
```python
class User(Base):
email: Mapped[str] = mapped_column(
String(255),
unique=True,
index=True # ← Index for fast lookups
)
```
**Trade-off:**
- Faster reads (queries using email are fast)
- Slower writes (index must be updated on insert/update)
- More disk space (index is stored separately)
### Connection Pooling
Database connections are expensive to create. Connection pooling reuses connections:
```python
DB_POOL_SIZE=20 # Max connections in pool
DB_MAX_OVERFLOW=10 # Extra connections when pool is full
DB_POOL_TIMEOUT=30 # Wait 30s for available connection
DB_POOL_RECYCLE=1800 # Recycle connections after 30min
```
**How it works:**
1. Request arrives
2. Backend grabs connection from pool
3. Executes query
4. Returns connection to pool (doesn't close it)
5. Next request reuses same connection
**Benefits:**
- Faster request handling (no connection overhead)
- Reduced database load (fewer connection negotiations)
- Better scalability (handle more concurrent requests)
### Redis Caching
Redis is used for frequently accessed data:
```python
# Without cache
user = await user_repo.find_by_id(user_id) # Database query every time
# With cache
user = await redis.get(f"user:{user_id}")
if not user:
user = await user_repo.find_by_id(user_id)
await redis.set(f"user:{user_id}", user, ex=300) # Cache for 5 min
```
**Cache invalidation strategies:**
1. Time-based (TTL) - expire after N seconds
2. Event-based - invalidate on update/delete
3. LRU (Least Recently Used) - Redis automatically evicts old keys
### Frontend Code Splitting
Vite automatically splits code by route:
```typescript
// routes/dashboard/page.tsx is lazy-loaded
const Dashboard = lazy(() => import('./routes/dashboard/page'));
```
**Benefits:**
- Faster initial page load (only load landing page)
- Subsequent navigation is fast (chunks are cached)
- Better bandwidth usage (don't load admin panel for regular users)
---
## Conclusion
This architecture prioritizes:
1. **Developer Experience** - Hot reload, type safety, linting
2. **Security** - Multiple defense layers, modern auth patterns
3. **Scalability** - Async operations, connection pooling, caching
4. **Maintainability** - Clear layer separation, domain-driven design
5. **Production Readiness** - Containerization, migrations, monitoring
Every design decision has trade-offs. This architecture favors correctness, security, and maintainability over raw performance (which can be optimized later if needed).
For more details on specific topics:
- Design patterns: [PATTERNS.md](./PATTERNS.md)
- Database schema: [DATABASE.md](./DATABASE.md)
- Security implementation: [SECURITY.md](./SECURITY.md)
- Step-by-step tutorial: [GETTING-STARTED.md](./GETTING-STARTED.md)