Skip to content

🏗️ Architecture technique

📋 Vue d'ensemble

L'architecture LIPAIX suit les principes de Clean Architecture avec des raccourcis pratiques pour faciliter la contribution et la maintenance.

🎯 Principes d'architecture

Clean Architecture (simplifiée)

Nous appliquons les principes fondamentaux de Clean Architecture :

  • ✅ Séparation des responsabilités - Chaque couche a un rôle défini
  • ✅ Inversion de dépendance - Les couches internes ne dépendent pas des externes
  • ✅ Testabilité - Code structuré pour faciliter les tests
  • ❌ Raccourcis - Simplifications pour faciliter la contribution

Structure en couches

┌─────────────────────────────────────┐
│           Presentation              │ ← Composants React, pages Next.js
├─────────────────────────────────────┤
│            Use Cases                │ ← Logique métier, commandes
├─────────────────────────────────────┤
│          Domain Layer               │ ← Entités, interfaces, types
├─────────────────────────────────────┤
│         Infrastructure              │ ← Base de données, APIs externes
└─────────────────────────────────────┘

🏗️ Organisation du monorepo

Structure des dossiers

lipaix-web-v3/
├── 📁 apps/
│   ├── 🌐 web/                    # Application Next.js + PayloadCMS
│   │   ├── 📁 src/
│   │   │   ├── 📁 app/           # App Router Next.js
│   │   │   ├── 📁 components/    # Composants React
│   │   │   ├── 📁 data/          # Couche infrastructure
│   │   │   └── 📁 core/          # Couche domaine
│   │   └── 📁 admin/             # Configuration PayloadCMS
│   └── 🤖 discord-bot/           # Bot Discord
│       ├── 📁 src/
│       │   ├── 📁 commands/      # Commandes slash
│       │   ├── 📁 services/      # Services métier
│       │   └── 📁 core/          # Logique principale
│       └── 📁 health/            # Health checks
├── 📁 shared/
│   └── 🔧 common/                # Code partagé
│       ├── 📁 src/
│       │   ├── 📁 core/          # Interfaces et types
│       │   ├── 📁 features/      # Logique métier partagée
│       │   └── 📁 utils/         # Utilitaires
│       └── 📁 package.json
└── 📁 docs/
    └── 📚 vitepress/             # Documentation technique

🔧 Couches techniques

1. Couche Présentation (Presentation Layer)

Responsabilité : Interface utilisateur et gestion des interactions

Technologies :

  • React 18 - Composants et hooks
  • Next.js 13+ - App Router et pages
  • TailwindCSS - Styling utility-first

Exemple de composant :

tsx
// Composant de présentation
export function EventCard({ event }: { event: Event }) {
  return (
    <div className="bg-white rounded-lg shadow-md p-6">
      <h3 className="text-xl font-bold">{event.title}</h3>
      <p className="text-gray-600">{event.description}</p>
      <div className="mt-4">
        <span className="text-sm text-blue-600">
          {event.date.toLocaleDateString('fr-FR')}
        </span>
      </div>
    </div>
  );
}

2. Couche Use Cases

Responsabilité : Logique métier et orchestration des opérations

Patterns utilisés :

  • Command Pattern - Encapsulation des actions métier
  • Query Pattern - Récupération de données

Exemple d'use case :

typescript
// Use case pour récupérer les prochains événements
export class GetNextFewEventsCommandUseCase {
  constructor(
    private eventsRepository: EventsRepository,
    private cacheService: CacheService
  ) {}

  async execute(count: number = 5): Promise<Event[]> {
    // Vérifier le cache d'abord
    const cached = await this.cacheService.get('next-events');
    if (cached) return cached;

    // Récupérer depuis le repository
    const events = await this.eventsRepository.getNextFewEvents(count);
    
    // Mettre en cache
    await this.cacheService.set('next-events', events, 300); // 5 min
    
    return events;
  }
}

3. Couche Domaine (Domain Layer)

Responsabilité : Entités métier, interfaces et types

Composants :

  • Entités - Objets métier avec comportement
  • Interfaces - Contrats entre couches
  • Types - Définitions TypeScript

Exemple d'entité :

typescript
// Entité Event avec comportement métier
export class Event {
  constructor(
    public readonly id: string,
    public readonly title: string,
    public readonly description: string,
    public readonly date: Date,
    public readonly maxPlayers: number,
    private currentPlayers: string[] = []
  ) {}

  // Comportement métier
  canJoin(playerId: string): boolean {
    return !this.currentPlayers.includes(playerId) && 
           this.currentPlayers.length < this.maxPlayers;
  }

  addPlayer(playerId: string): void {
    if (this.canJoin(playerId)) {
      this.currentPlayers.push(playerId);
    }
  }

  get availableSpots(): number {
    return this.maxPlayers - this.currentPlayers.length;
  }
}

4. Couche Infrastructure

Responsabilité : Accès aux données et services externes

Composants :

  • Repositories - Accès aux données
  • Services - Intégrations externes
  • Adapters - Adaptation des données

Exemple de repository :

typescript
// Interface du repository
export interface EventsRepository {
  getNextFewEvents(count: number): Promise<Event[]>;
  getById(id: string): Promise<Event | null>;
  save(event: Event): Promise<void>;
}

// Implémentation avec PayloadCMS
export class PayloadLocalAPIEventsRepository implements EventsRepository {
  constructor(private payload: Payload) {}

  async getNextFewEvents(count: number): Promise<Event[]> {
    const response = await this.payload.find({
      collection: 'shows',
      where: {
        date: {
          greater_than: new Date().toISOString()
        }
      },
      limit: count,
      sort: 'date'
    });

    return response.docs.map(doc => this.mapToEvent(doc));
  }

  private mapToEvent(doc: any): Event {
    return new Event(
      doc.id,
      doc.title,
      doc.description,
      new Date(doc.date),
      doc.maxPlayers
    );
  }
}

🔄 Injection de dépendances

Pattern GlobalRef (simplifié)

Pour éviter la complexité d'un container DI complet, nous utilisons un pattern simplifié :

typescript
// Service global avec lazy initialization
export class GlobalRef<T> {
  private static instance: any;
  
  static get<T>(factory: () => T): T {
    if (!GlobalRef.instance) {
      GlobalRef.instance = factory();
    }
    return GlobalRef.instance;
  }
}

// Utilisation
export const eventsRepository = GlobalRef.get<EventsRepository>(
  () => new PayloadLocalAPIEventsRepository(payload)
);

🗄️ Gestion des données

Pattern Repository

Chaque entité métier a son repository :

  • EventsRepository - Gestion des événements
  • UsersRepository - Gestion des utilisateurs
  • AvailabilitiesRepository - Gestion des disponibilités

Cache et performance

  • Redis - Cache des données fréquemment accédées
  • Stratégie cache-first - Vérifier le cache avant la base
  • TTL configurable - Expiration automatique des données

🔐 Sécurité et accès

Contrôle d'accès PayloadCMS

typescript
// Configuration des rôles et permissions
export const Show: CollectionConfig = {
  access: {
    read: () => true, // Lecture publique
    create: ({ req: { user } }) => {
      return user?.role === 'admin';
    },
    update: ({ req: { user } }) => {
      return user?.role === 'admin';
    }
  }
};

Validation des données

  • Zod - Validation des schémas
  • PayloadCMS - Validation côté serveur
  • React Hook Form - Validation côté client

🧪 Testabilité

Structure des tests

__tests__/
├── 📁 unit/              # Tests unitaires
├── 📁 integration/       # Tests d'intégration
└── 📁 e2e/              # Tests end-to-end

Mocks et stubs

typescript
// Mock du repository pour les tests
export class MockEventsRepository implements EventsRepository {
  private events: Event[] = [];

  async getNextFewEvents(count: number): Promise<Event[]> {
    return this.events.slice(0, count);
  }

  addEvent(event: Event): void {
    this.events.push(event);
  }
}

🚀 Déploiement et infrastructure

Services Railway

  • Web App - Next.js + PayloadCMS
  • Discord Bot - Node.js standalone
  • Database - PostgreSQL
  • Cache - Redis
  • Documentation - VitePress

Variables d'environnement

bash
# Base de données
DATABASE_URL=postgresql://...
REDIS_URL=redis://...

# Discord
DISCORD_TOKEN=...
DISCORD_CLIENT_ID=...

# PayloadCMS
PAYLOAD_SECRET=...

📊 Monitoring et observabilité

Health checks

  • /api/health - Endpoint de santé
  • Sentry - Monitoring des erreurs
  • Logs structurés - Traçabilité des opérations

Métriques

  • Temps de réponse des APIs
  • Taux d'erreur par endpoint
  • Utilisation des ressources (CPU, mémoire)

🔄 Évolution de l'architecture

Principes de refactoring

  1. Extract Method - Extraire la logique complexe
  2. Extract Class - Séparer les responsabilités
  3. Introduce Interface - Découpler les couches
  4. Move Method - Placer le code au bon endroit

Signaux de refactoring

  • Classes trop grandes (>200 lignes)
  • Méthodes trop longues (>20 lignes)
  • Duplication de code
  • Violation du principe de responsabilité unique

🎯 Bonnes pratiques

Do's

  • Séparer les responsabilités - Chaque classe a un rôle unique
  • Utiliser les interfaces - Découpler les implémentations
  • Tester les use cases - Logique métier testable
  • Valider les données - Entrées et sorties validées

Don'ts

  • Mélanger les couches - Présentation dans le domaine
  • Créer des dépendances circulaires
  • Ignorer la gestion d'erreurs
  • Oublier la validation des données

🚀 Prochaines étapes


Cette architecture évolue avec le projet. N'hésitez pas à proposer des améliorations ! 🚀

Released under the MIT License.