Skip to content

🏗️ Architecture

🎯 Overview

What is Architecture?

Architecture in software development refers to the fundamental structure of a system - how components are organized, how they communicate, and how they work together to achieve the system's goals. For LIPAIX, we follow Clean Architecture principles with practical simplifications.

Why Clean Architecture?

  1. 🧹 Separation of Concerns - Clear boundaries between different parts of the system
  2. 🔄 Dependency Inversion - High-level modules don't depend on low-level modules
  3. 🧪 Testability - Easy to test individual components in isolation
  4. 📚 Maintainability - Clear structure makes code easier to understand and modify
  5. 🚀 Scalability - Architecture supports growth and new features

🏢 Monorepo Structure

High-Level Organization

mermaid
graph TB
    subgraph "LIPAIX Monorepo"
        APPS[apps/]
        SHARED[shared/]
        DOCS[docs/]
        ROOT[Root Config]
    end
    
    subgraph "Applications"
        WEB[web/ - Next.js App]
        ADMIN[admin/ - PayloadCMS Admin]
        DISCORD[discord-bot/ - Discord Bot]
    end
    
    subgraph "Shared Resources"
        COMMON[common/ - Core Utilities]
        TYPES[Type Definitions]
        SERVICES[Shared Services]
    end
    
    subgraph "Documentation"
        VITEPRESS[VitePress Docs]
        README[README Files]
        GUIDES[Development Guides]
    end
    
    ROOT --> APPS
    ROOT --> SHARED
    ROOT --> DOCS
    
    APPS --> WEB
    APPS --> ADMIN
    APPS --> DISCORD
    
    SHARED --> COMMON
    SHARED --> TYPES
    SHARED --> SERVICES
    
    DOCS --> VITEPRESS
    DOCS --> README
    DOCS --> GUIDES

Directory Structure

mermaid
graph LR
    subgraph "Root Level"
        PKG[package.json]
        WORKSPACE[pnpm-workspace.yaml]
        TS[tsconfig.json]
        GIT[.gitignore]
    end
    
    subgraph "apps/"
        WEB_DIR[web/]
        ADMIN_DIR[admin/]
        DISCORD_DIR[discord-bot/]
    end
    
    subgraph "shared/"
        COMMON_DIR[common/]
    end
    
    subgraph "docs/"
        VITEPRESS_DIR[vitepress/]
    end
    
    PKG --> WORKSPACE
    WORKSPACE --> WEB_DIR
    WORKSPACE --> ADMIN_DIR
    WORKSPACE --> DISCORD_DIR
    WORKSPACE --> COMMON_DIR
    WORKSPACE --> VITEPRESS_DIR

🧩 Clean Architecture Principles

Layered Architecture

mermaid
graph TB
    subgraph "Presentation Layer"
        UI[UI Components]
        PAGES[Pages/Routes]
        FORMS[Forms & Validation]
    end
    
    subgraph "Application Layer"
        USECASES[Use Cases]
        CONTROLLERS[Controllers]
        MIDDLEWARE[Middleware]
    end
    
    subgraph "Domain Layer"
        ENTITIES[Entities]
        VALUE_OBJECTS[Value Objects]
        DOMAIN_SERVICES[Domain Services]
    end
    
    subgraph "Infrastructure Layer"
        REPOSITORIES[Repositories]
        EXTERNAL_APIS[External APIs]
        DATABASE[Database]
    end
    
    UI --> PAGES
    PAGES --> FORMS
    FORMS --> USECASES
    USECASES --> CONTROLLERS
    CONTROLLERS --> MIDDLEWARE
    MIDDLEWARE --> ENTITIES
    ENTITIES --> VALUE_OBJECTS
    VALUE_OBJECTS --> DOMAIN_SERVICES
    DOMAIN_SERVICES --> REPOSITORIES
    REPOSITORIES --> EXTERNAL_APIS
    EXTERNAL_APIS --> DATABASE

Dependency Flow

mermaid
flowchart TD
    A[Presentation Layer] --> B[Application Layer]
    B --> C[Domain Layer]
    C --> D[Infrastructure Layer]
    
    E[External Dependencies] --> D
    F[Database] --> D
    
    style A fill:#e1f5fe
    style B fill:#f3e5f5
    style C fill:#e8f5e8
    style D fill:#fff3e0

🔌 Dependency Injection

Global Reference Pattern

Instead of complex DI containers, we use a simplified Global Reference pattern:

mermaid
graph LR
    subgraph "GlobalRef Pattern"
        GLOBAL[GlobalRef]
        INSTANCES[Instances]
        SINGLETONS[Singletons]
    end
    
    subgraph "Usage"
        COMPONENT[Component]
        SERVICE[Service]
        USECASE[Use Case]
    end
    
    GLOBAL --> INSTANCES
    GLOBAL --> SINGLETONS
    
    COMPONENT --> GLOBAL
    SERVICE --> GLOBAL
    USECASE --> GLOBAL

Implementation Example

typescript
export class GlobalRef<T> {
  private static instances = new Map<string, any>()
  
  constructor(private key: string) {}
  
  get(): T {
    if (!GlobalRef.instances.has(this.key)) {
      throw new Error(`No instance found for key: ${this.key}`)
    }
    return GlobalRef.instances.get(this.key)
  }
  
  set(instance: T): void {
    GlobalRef.instances.set(this.key, instance)
  }
  
  has(): boolean {
    return GlobalRef.instances.has(this.key)
  }
  
  clear(): void {
    GlobalRef.instances.delete(this.key)
  }
}

// Usage example
export const eventRepositoryRef = new GlobalRef<EventRepository>('EventRepository')
export const playerRepositoryRef = new GlobalRef<PlayerRepository>('PlayerRepository')

🗄️ Repository Pattern

Data Access Abstraction

mermaid
graph TB
    subgraph "Repository Interface"
        REPO_INTERFACE[Repository Interface]
        METHODS[CRUD Methods]
        QUERIES[Query Methods]
    end
    
    subgraph "Implementations"
        MYSQL_REPO[MySQL Repository]
        PAYLOAD_REPO[PayloadCMS Repository]
        MOCK_REPO[Mock Repository]
    end
    
    subgraph "Usage"
        USECASE[Use Case]
        SERVICE[Service]
        CONTROLLER[Controller]
    end
    
    REPO_INTERFACE --> METHODS
    REPO_INTERFACE --> QUERIES
    
    METHODS --> MYSQL_REPO
    METHODS --> PAYLOAD_REPO
    METHODS --> MOCK_REPO
    
    USECASE --> REPO_INTERFACE
    SERVICE --> REPO_INTERFACE
    CONTROLLER --> REPO_INTERFACE

Repository Implementation

typescript
export interface EventRepository {
  findById(id: string): Promise<Event | null>
  findByDateRange(start: Date, end: Date): Promise<Event[]>
  save(event: Event): Promise<Event>
  delete(id: string): Promise<boolean>
  findUpcoming(limit?: number): Promise<Event[]>
}

export class MySQLEventRepository implements EventRepository {
  constructor(private mysqlService: MySQLService) {}
  
  async findById(id: string): Promise<Event | null> {
    const query = 'SELECT * FROM events WHERE id = ?'
    const [rows] = await this.mysqlService.execute(query, [id])
    return rows.length > 0 ? this.adaptToEntity(rows[0]) : null
  }
  
  async findByDateRange(start: Date, end: Date): Promise<Event[]> {
    const query = 'SELECT * FROM events WHERE date BETWEEN ? AND ? ORDER BY date'
    const [rows] = await this.mysqlService.execute(query, [start, end])
    return rows.map(row => this.adaptToEntity(row))
  }
  
  // ... other methods
}

🎯 Use Case Pattern

Business Logic Encapsulation

mermaid
graph TD
    subgraph "Use Case"
        INPUT[Input Data]
        VALIDATION[Validation]
        BUSINESS_LOGIC[Business Logic]
        OUTPUT[Output Data]
    end
    
    subgraph "Dependencies"
        REPOSITORIES[Repositories]
        SERVICES[Services]
        VALIDATORS[Validators]
    end
    
    subgraph "Flow"
        VALIDATE[Validate Input]
        PROCESS[Process Business Logic]
        PERSIST[Persist Changes]
        RETURN[Return Result]
    end
    
    INPUT --> VALIDATION
    VALIDATION --> BUSINESS_LOGIC
    BUSINESS_LOGIC --> OUTPUT
    
    VALIDATION --> VALIDATORS
    BUSINESS_LOGIC --> REPOSITORIES
    BUSINESS_LOGIC --> SERVICES
    
    VALIDATE --> PROCESS
    PROCESS --> PERSIST
    PERSIST --> RETURN

Use Case Example

typescript
export class GetNextFewEventsCommandUseCase {
  constructor(
    private eventRepository: EventRepository,
    private dateService: DateService
  ) {}
  
  async execute(command: GetNextFewEventsCommand): Promise<GetNextFewEventsResult> {
    try {
      // Validate input
      if (command.count < 1 || command.count > 50) {
        return {
          success: false,
          error: 'Count must be between 1 and 50'
        }
      }
      
      // Get current date
      const now = this.dateService.now()
      
      // Find upcoming events
      const events = await this.eventRepository.findUpcoming(command.count)
      
      // Filter and sort
      const upcomingEvents = events
        .filter(event => event.date > now)
        .sort((a, b) => a.date.getTime() - b.date.getTime())
        .slice(0, command.count)
      
      return {
        success: true,
        events: upcomingEvents
      }
    } catch (error) {
      return {
        success: false,
        error: error.message
      }
    }
  }
}

🔄 Data Flow

Request Processing Flow

mermaid
sequenceDiagram
    participant C as Client
    participant P as Pages
    participant UC as Use Cases
    participant R as Repositories
    participant DB as Database
    
    C->>P: HTTP Request
    P->>UC: Execute Use Case
    UC->>R: Query Data
    R->>DB: SQL Query
    DB-->>R: Data Result
    R-->>UC: Domain Objects
    UC-->>P: Business Result
    P-->>C: HTTP Response

Component Communication

mermaid
graph LR
    subgraph "Frontend"
        PAGE[Page Component]
        COMPONENT[UI Component]
        HOOK[Custom Hook]
    end
    
    subgraph "Backend"
        API[API Route]
        USECASE[Use Case]
        REPOSITORY[Repository]
    end
    
    subgraph "Data"
        DATABASE[(Database)]
        CACHE[Cache Layer]
        EXTERNAL[External APIs]
    end
    
    PAGE --> COMPONENT
    COMPONENT --> HOOK
    HOOK --> API
    API --> USECASE
    USECASE --> REPOSITORY
    REPOSITORY --> DATABASE
    REPOSITORY --> CACHE
    REPOSITORY --> EXTERNAL

📦 Package Architecture

Module Dependencies

mermaid
graph TD
    subgraph "Core Layer"
        CORE[Core]
        VALUE_OBJECTS[Value Objects]
        ENTITIES[Entities]
    end
    
    subgraph "Feature Layer"
        EVENTS[Events Feature]
        PLAYERS[Players Feature]
        AVAILABILITIES[Availabilities Feature]
    end
    
    subgraph "Infrastructure Layer"
        MYSQL[MySQL Services]
        PAYLOAD[PayloadCMS Services]
        DISCORD[Discord Services]
    end
    
    subgraph "Application Layer"
        WEB[Web App]
        ADMIN[Admin App]
        BOT[Discord Bot]
    end
    
    CORE --> VALUE_OBJECTS
    CORE --> ENTITIES
    
    EVENTS --> CORE
    PLAYERS --> CORE
    AVAILABILITIES --> CORE
    
    MYSQL --> EVENTS
    MYSQL --> PLAYERS
    MYSQL --> AVAILABILITIES
    
    PAYLOAD --> EVENTS
    PAYLOAD --> PLAYERS
    PAYLOAD --> AVAILABILITIES
    
    DISCORD --> EVENTS
    DISCORD --> AVAILABILITIES
    
    WEB --> EVENTS
    WEB --> PLAYERS
    WEB --> AVAILABILITIES
    
    ADMIN --> EVENTS
    ADMIN --> PLAYERS
    ADMIN --> AVAILABILITIES
    
    BOT --> EVENTS
    BOT --> AVAILABILITIES

Import Structure

typescript
// Feature imports
import { GetNextFewEventsCommandUseCase } from '@/features/events/GetNextFewEventsCommandUseCase'
import { EventCard } from '@/components/events/EventCard'

// Shared imports
import { DateService } from '@/shared/common/src/features/configuration/DateService'

// UI imports
import { Button } from '@/components/ui/Button'
import { useEvents } from '@/hooks/useEvents'

⚙️ Configuration Management

Environment Configuration

mermaid
graph LR
    subgraph "Configuration Sources"
        ENV[Environment Variables]
        CONFIG[Config Files]
        SECRETS[Secrets Management]
    end
    
    subgraph "Configuration Service"
        CONFIG_SERVICE[Configuration Service]
        VALIDATION[Validation]
        DEFAULTS[Default Values]
    end
    
    subgraph "Usage"
        APP[Application]
        SERVICES[Services]
        MIDDLEWARE[Middleware]
    end
    
    ENV --> CONFIG_SERVICE
    CONFIG --> CONFIG_SERVICE
    SECRETS --> CONFIG_SERVICE
    
    CONFIG_SERVICE --> VALIDATION
    CONFIG_SERVICE --> DEFAULTS
    
    CONFIG_SERVICE --> APP
    CONFIG_SERVICE --> SERVICES
    CONFIG_SERVICE --> MIDDLEWARE

Configuration Implementation

typescript
export interface ConfigurationRepository {
  getDatabaseUrl(): string
  getDiscordToken(): string
  getEnvironment(): 'development' | 'staging' | 'production'
  getFeatureFlag(key: string): boolean
}

export class EnvVarConfigurationRepository implements ConfigurationRepository {
  getDatabaseUrl(): string {
    const url = process.env.DATABASE_URL
    if (!url) {
      throw new Error('DATABASE_URL environment variable is required')
    }
    return url
  }
  
  getDiscordToken(): string {
    const token = process.env.DISCORD_TOKEN
    if (!token) {
      throw new Error('DISCORD_TOKEN environment variable is required')
    }
    return token
  }
  
  getEnvironment(): 'development' | 'staging' | 'production' {
    return (process.env.NODE_ENV as any) || 'development'
  }
  
  getFeatureFlag(key: string): boolean {
    return process.env[`FEATURE_${key.toUpperCase()}`] === 'true'
  }
}

🧪 Testing Strategy

Current Status: No Tests Yet ⚠️

Honest Assessment: As of now, the LIPAIX project does not have any automated tests implemented. This is a known technical debt that needs to be addressed as the project matures.

Why We Don't Have Tests Yet

  1. 🚧 Rapid Development - The project is still in active development with frequent changes
  2. 🎯 MVP Focus - Priority has been on getting core functionality working
  3. 👥 Small Team - Limited resources to implement comprehensive testing
  4. 🔄 Evolving Architecture - The codebase structure is still being refined

Testing Plan for the Future

mermaid
graph TD
    subgraph "Phase 1: Foundation"
        UNIT_TESTS[Unit Tests]
        TEST_UTILS[Test Utilities]
        MOCK_SYSTEM[Mock System]
    end
    
    subgraph "Phase 2: Integration"
        API_TESTS[API Tests]
        DB_TESTS[Database Tests]
        AUTH_TESTS[Authentication Tests]
    end
    
    subgraph "Phase 3: End-to-End"
        E2E_TESTS[E2E Tests]
        USER_FLOWS[User Flow Tests]
        CROSS_BROWSER[Cross-browser Tests]
    end
    
    UNIT_TESTS --> API_TESTS
    API_TESTS --> E2E_TESTS
    TEST_UTILS --> UNIT_TESTS
    MOCK_SYSTEM --> UNIT_TESTS
    DB_TESTS --> API_TESTS
    AUTH_TESTS --> API_TESTS
    USER_FLOWS --> E2E_TESTS
    CROSS_BROWSER --> E2E_TESTS

Proposed Testing Stack

Test TypeToolPurposePriority
Unit TestsJestBusiness logic validationHigh
API TestsJest + SupertestBackend API validationHigh
Component TestsReact Testing LibraryFrontend component validationMedium
E2E TestsPlaywrightUser journey validationLow
Database TestsJest + Test DBData layer validationMedium

What We'll Test First

  1. 🧪 Core Business Logic - Use cases, services, and utilities
  2. 🔌 API Endpoints - REST and GraphQL endpoints
  3. 🗄️ Database Operations - Repository methods and data validation
  4. 🎨 UI Components - Critical user interface components
  5. 🔐 Authentication - Login, permissions, and access control

Testing Guidelines (When We Start)

typescript
// Example of how we'll structure tests when we implement them
describe('EventService', () => {
  it('should create an event with valid data', async () => {
    // Test implementation will go here
  })
  
  it('should reject invalid event data', async () => {
    // Test implementation will go here
  })
})

Getting Started with Testing

When the team is ready to implement testing:

  1. 📚 Learn Testing Tools - Jest, React Testing Library, Playwright
  2. 🏗️ Set Up Test Infrastructure - Test database, mock services
  3. 📝 Write First Tests - Start with simple, critical functionality
  4. 🔄 Integrate with CI/CD - Run tests on every commit
  5. 📊 Monitor Coverage - Track test coverage metrics

Note: This section will be updated as testing is implemented. For now, manual testing and code review are our primary quality assurance methods.

🚀 Performance Considerations

Performance Architecture

mermaid
graph TB
    subgraph "Frontend Performance"
        IMAGE_OPT[Image Optimization]
        CODE_SPLIT[Code Splitting]
        CACHING[Browser Caching]
    end
    
    subgraph "Backend Performance"
        DB_OPT[Database Optimization]
        API_CACHE[API Caching]
        ASYNC[Async Processing]
    end
    
    subgraph "Infrastructure"
        CDN[CDN]
        LOAD_BAL[Load Balancing]
        AUTO_SCALE[Auto Scaling]
    end
    
    IMAGE_OPT --> CDN
    CODE_SPLIT --> LOAD_BAL
    CACHING --> CDN
    DB_OPT --> AUTO_SCALE
    API_CACHE --> LOAD_BAL
    ASYNC --> AUTO_SCALE

Caching Strategy

typescript
export class CachedData<T> {
  private cache = new Map<string, { data: T; timestamp: number }>()
  private ttl: number
  
  constructor(ttlMinutes: number = 5) {
    this.ttl = ttlMinutes * 60 * 1000
  }
  
  get(key: string): T | null {
    const cached = this.cache.get(key)
    if (!cached) return null
    
    if (Date.now() - cached.timestamp > this.ttl) {
      this.cache.delete(key)
      return null
    }
    
    return cached.data
  }
  
  set(key: string, data: T): void {
    this.cache.set(key, { data, timestamp: Date.now() })
  }
  
  clear(): void {
    this.cache.clear()
  }
}

🔒 Security Architecture

Security Layers

mermaid
graph TB
    subgraph "Application Security"
        AUTH[Authentication]
        AUTHORIZATION[Authorization]
        VALIDATION[Input Validation]
    end
    
    subgraph "Infrastructure Security"
        HTTPS[HTTPS/TLS]
        FIREWALL[Firewall Rules]
        SECRETS[Secrets Management]
    end
    
    subgraph "Data Security"
        ENCRYPTION[Data Encryption]
        BACKUP[Secure Backups]
        AUDIT[Audit Logging]
    end
    
    AUTH --> HTTPS
    AUTHORIZATION --> FIREWALL
    VALIDATION --> SECRETS
    ENCRYPTION --> BACKUP
    BACKUP --> AUDIT

Security Implementation

typescript
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  const response = NextResponse.next()
  
  // Security headers
  response.headers.set('X-Frame-Options', 'DENY')
  response.headers.set('X-Content-Type-Options', 'nosniff')
  response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin')
  response.headers.set('Permissions-Policy', 'camera=(), microphone=(), geolocation=()')
  
  // Content Security Policy
  response.headers.set(
    'Content-Security-Policy',
    "default-src 'self'; script-src 'self' 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline';"
  )
  
  return response
}

📊 Monitoring & Observability

Monitoring Stack

mermaid
graph LR
    subgraph "Application Monitoring"
        SENTRY[Sentry]
        LOGS[Structured Logs]
        METRICS[Custom Metrics]
    end
    
    subgraph "Infrastructure Monitoring"
        RAILWAY[Railway Metrics]
        HEALTH[Health Checks]
        ALERTS[Alerting]
    end
    
    subgraph "Performance Monitoring"
        CORE_VITALS[Core Web Vitals]
        API_METRICS[API Response Times]
        ERROR_RATES[Error Rates]
    end
    
    SENTRY --> LOGS
    LOGS --> METRICS
    METRICS --> HEALTH
    HEALTH --> ALERTS
    CORE_VITALS --> API_METRICS
    API_METRICS --> ERROR_RATES

Health Check Implementation

typescript
import { NextResponse } from 'next/server'
import { healthCheck } from '@/utils/health'

export async function GET() {
  try {
    const health = await healthCheck()
    
    if (health.status === 'healthy') {
      return NextResponse.json(health, { status: 200 })
    } else {
      return NextResponse.json(health, { status: 503 })
    }
  } catch (error) {
    return NextResponse.json(
      {
        status: 'unhealthy',
        error: error.message,
        timestamp: new Date().toISOString(),
      },
      { status: 500 }
    )
  }
}

🚀 Deployment Architecture

Deployment Flow

mermaid
flowchart TD
    A[Code Push] --> B[GitHub Actions]
    B --> C[Run Tests]
    C --> D{Tests Pass?}
    D -->|No| E[Fail Build]
    D -->|Yes| F[Build Application]
    F --> G[Security Scan]
    G --> H{Security OK?}
    H -->|No| I[Fail Build]
    H -->|Yes| J[Deploy to Railway]
    J --> K[Health Check]
    K --> L{Health OK?}
    L -->|No| M[Rollback]
    L -->|Yes| N[Success]
    
    E --> O[Notify Team]
    I --> O
    M --> O
    N --> P[Notify Success]

Environment Management

mermaid
graph LR
    subgraph "Environments"
        DEV[Development]
        STAGING[Staging]
        PROD[Production]
    end
    
    subgraph "Configuration"
        DEV_CONFIG[Dev Config]
        STAGING_CONFIG[Staging Config]
        PROD_CONFIG[Prod Config]
    end
    
    subgraph "Deployment"
        DEV_DEPLOY[Local Dev]
        STAGING_DEPLOY[Staging Deploy]
        PROD_DEPLOY[Production Deploy]
    end
    
    DEV --> DEV_CONFIG
    STAGING --> STAGING_CONFIG
    PROD --> PROD_CONFIG
    
    DEV_CONFIG --> DEV_DEPLOY
    STAGING_CONFIG --> STAGING_DEPLOY
    PROD_CONFIG --> PROD_DEPLOY

🏗️ Architecture Summary

The LIPAIX project follows Clean Architecture principles with practical simplifications. We use a monorepo structure with clear separation of concerns, dependency injection through GlobalRef pattern, and comprehensive testing strategies.

Key architectural features:

  • Monorepo Structure - Organized apps, shared code, and documentation
  • Clean Architecture - Layered approach with clear dependencies
  • Dependency Injection - Simplified GlobalRef pattern for singletons
  • Repository Pattern - Abstract data access layer
  • Use Case Pattern - Encapsulated business logic
  • Comprehensive Testing - Unit, integration, and E2E testing

Released under the MIT License.