You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

30 KiB

🏛️ Architecture Logicielle - BricoLoc Evolution

Document de Référence Architecture
Version 1.0 - Octobre 2025


📋 Table des Matières

  1. Vue d'Ensemble
  2. Principes Architecturaux
  3. Patterns Utilisés
  4. Architecture Legacy
  5. Architecture Moderne
  6. Comparaison Architecturale
  7. Décisions Techniques
  8. Qualités Architecturales

🎯 Vue d'Ensemble

Contexte

BricoLoc est une plateforme de location d'outils en ligne. L'application actuelle (Legacy) souffre de plusieurs problèmes architecturaux accumulés depuis 2013 :

  • Architecture monolithique avec couplage fort
  • Logique métier éparpillée (frontend, backend, base de données)
  • Maintenance difficile (régressions fréquentes)
  • Performance dégradée (temps de réponse > 3s)
  • Scalabilité limitée

Objectif

Démontrer une refonte architecturale complète en appliquant les principes et patterns modernes d'architecture logicielle.

Approche

Création de deux applications côte à côte :

  1. Legacy : Simulation du système actuel avec ses problèmes
  2. Moderne : Nouvelle architecture avec résolution des problèmes

🏛️ Principes Architecturaux

SOLID Principles

S - Single Responsibility Principle

Une classe/module ne doit avoir qu'une seule raison de changer.

Application :

  • Séparation claire entre couches (UI, Business Logic, Data)
  • Composants React mono-responsabilité
  • Services dédiés par domaine métier

O - Open/Closed Principle

Ouvert à l'extension, fermé à la modification.

Application :

  • Utilisation d'interfaces et abstractions
  • Pattern Strategy pour comportements variables
  • Plugins et extensions sans modifier le code existant

L - Liskov Substitution Principle

Les classes dérivées doivent être substituables à leurs classes de base.

Application :

  • Interfaces respectées par toutes les implémentations
  • Repository pattern avec différentes sources de données

I - Interface Segregation Principle

Les clients ne doivent pas dépendre d'interfaces qu'ils n'utilisent pas.

Application :

  • Interfaces spécifiques par contexte
  • Composants avec props minimales

D - Dependency Inversion Principle

Dépendre des abstractions, pas des implémentations.

Application :

  • Injection de dépendances
  • Abstractions (interfaces) entre couches

Clean Architecture

┌─────────────────────────────────────────────┐
│           UI Layer (React Components)        │
│  - Pages, Components, Hooks                 │
└──────────────────┬──────────────────────────┘
                   │
┌──────────────────▼──────────────────────────┐
│      Application Layer (Use Cases)          │
│  - Services, Business Logic                 │
└──────────────────┬──────────────────────────┘
                   │
┌──────────────────▼──────────────────────────┐
│       Domain Layer (Entities)               │
│  - Models, Types, Business Rules            │
└──────────────────┬──────────────────────────┘
                   │
┌──────────────────▼──────────────────────────┐
│    Infrastructure Layer (Data Sources)      │
│  - Repositories, APIs, Database             │
└─────────────────────────────────────────────┘

Règles :

  • Les dépendances pointent vers l'intérieur
  • Le domaine ne dépend de rien
  • L'infrastructure dépend du domaine (inversion)

Separation of Concerns

Chaque partie du système a une responsabilité claire :

Couche Responsabilité Exemples
UI Présentation Composants React, pages
Application Orchestration Services, use cases
Domain Métier Entities, business rules
Infrastructure Technique API calls, DB access

🎨 Patterns Utilisés

1. Repository Pattern

Objectif : Abstraire l'accès aux données

// Interface (Domain Layer)
interface OutilRepository {
  findAll(filters?: OutilFilters): Promise<Outil[]>
  findById(id: string): Promise<Outil | null>
  create(outil: CreateOutilDTO): Promise<Outil>
  update(id: string, data: UpdateOutilDTO): Promise<Outil>
  delete(id: string): Promise<void>
}

// Implémentation (Infrastructure Layer)
class SupabaseOutilRepository implements OutilRepository {
  constructor(private supabase: SupabaseClient) {}
  
  async findAll(filters?: OutilFilters): Promise<Outil[]> {
    // Implémentation Supabase
  }
  
  // ... autres méthodes
}

Bénéfices :

  • Changement de source de données sans impact
  • Tests facilités (mock du repository)
  • Logique data isolée

2. Service Layer Pattern

Objectif : Centraliser la logique métier

// Service Layer
class ReservationService {
  constructor(
    private reservationRepo: ReservationRepository,
    private stockService: StockService,
    private pricingService: PricingService
  ) {}
  
  async createReservation(data: CreateReservationDTO): Promise<Reservation> {
    // 1. Vérifier disponibilité
    const isAvailable = await this.stockService.checkAvailability(
      data.outilId, 
      data.entrepotId, 
      data.dateDebut, 
      data.dateFin
    )
    
    if (!isAvailable) {
      throw new UnavailableError()
    }
    
    // 2. Calculer prix
    const prix = await this.pricingService.calculate(data)
    
    // 3. Créer réservation
    const reservation = await this.reservationRepo.create({
      ...data,
      prixTotal: prix
    })
    
    // 4. Mettre à jour stock
    await this.stockService.reserve(data.outilId, data.entrepotId)
    
    return reservation
  }
}

Bénéfices :

  • Logique métier centralisée et réutilisable
  • Orchestration des opérations complexes
  • Tests unitaires faciles

3. Dependency Injection

Objectif : Découpler les dépendances

// Container DI (simplifié)
class ServiceContainer {
  private static instance: ServiceContainer
  
  private services = new Map<string, any>()
  
  register<T>(key: string, factory: () => T): void {
    this.services.set(key, factory)
  }
  
  resolve<T>(key: string): T {
    const factory = this.services.get(key)
    return factory()
  }
}

// Enregistrement
container.register('outilRepository', () => 
  new SupabaseOutilRepository(supabaseClient)
)

container.register('outilService', () => 
  new OutilService(
    container.resolve('outilRepository')
  )
)

// Utilisation
const outilService = container.resolve<OutilService>('outilService')

4. Factory Pattern

Objectif : Créer des objets complexes

class ReservationFactory {
  static create(dto: CreateReservationDTO, user: User): Reservation {
    return {
      id: generateId(),
      utilisateurId: user.id,
      outilId: dto.outilId,
      statut: 'en_attente_paiement',
      createdAt: new Date(),
      // ... autres champs calculés
    }
  }
  
  static createFromLegacy(legacyData: any): Reservation {
    // Adapter les anciennes données
  }
}

5. Observer Pattern (Realtime)

Objectif : Réagir aux changements en temps réel

// Supabase Realtime = Observer Pattern
const stockChannel = supabase
  .channel('stocks')
  .on('postgres_changes', 
    { 
      event: 'UPDATE', 
      schema: 'public', 
      table: 'stocks' 
    },
    (payload) => {
      // Notifier les observers (composants React)
      updateStockCache(payload.new)
    }
  )
  .subscribe()

🕰️ Architecture Legacy

Vue d'Ensemble

graph TD
    A[Client Browser] --> B[Apache HTTP Reverse Proxy]
    B --> C[Tomcat - Spring Framework Frontend]
    C --> D[WebLogic - Java EE Backend]
    D --> E[Oracle Database]
    D --> F[MySQL Cache DB]
    C --> F
    
    G[Client Lourd Stocks] --> H[WCF Service]
    H --> E
    
    I[SAP ERP] --> J[Batch CSV]
    J --> E

Problèmes Architecturaux

1. Couplage Fort

  • Frontend accède directement à la BDD (court-circuite backend)
  • Logique métier dans les procédures stockées PL/SQL
  • Dépendance directe aux technologies (Oracle, WebLogic)

2. Monolithe Complexe

  • Tout dans une seule application
  • Déploiement all-or-nothing
  • Scaling vertical uniquement

3. Maintenance Difficile

  • Code spaghetti accumulé sur 10+ ans
  • Tables avec 150+ colonnes
  • Documentation obsolète
  • Pas de tests automatisés

4. Performance

  • Synchronisation stocks quotidienne (décalage 24h)
  • Pas de cache efficace
  • Requêtes SQL non optimisées

Architecture Moderne

Style Architectural : Microservices Hybride + Clean Architecture

L'architecture moderne combine :

  • Microservices pour la décomposition fonctionnelle et l'autonomie
  • Clean Architecture (couches) à l'intérieur de chaque microservice
  • API Gateway pour l'orchestration
  • Event-Driven pour la communication asynchrone

Vue d'Ensemble - Architecture Microservices

graph TB
    subgraph "Client Layer"
        A[Web App - Next.js]
        B[Mobile App - Future]
    end
    
    subgraph "API Gateway Layer"
        C[API Gateway / BFF]
    end
    
    A --> C
    B --> C
    
    subgraph "Microservices Layer"
        D[Auth Service<br/>Supabase Auth]
        E[Catalogue Service<br/>Next.js API + Supabase]
        F[Reservation Service<br/>Next.js API + Supabase]
        G[Payment Service<br/>Next.js API + Stripe Mock]
        H[Notification Service<br/>Next.js API + Resend]
        I[Inventory Service<br/>Next.js API + Supabase Realtime]
    end
    
    C --> D
    C --> E
    C --> F
    C --> G
    C --> H
    C --> I
    
    subgraph "Data Layer"
        J[(Supabase PostgreSQL<br/>+ Realtime)]
        K[Cache - Redis/Supabase]
        L[Storage - Supabase Storage]
    end
    
    E --> J
    F --> J
    I --> J
    E --> K
    F --> K
    I --> L
    
    subgraph "Event Bus"
        M[Event Bus<br/>Supabase Realtime Channels]
    end
    
    F --> M
    I --> M
    M --> H

Décomposition en Microservices

🔐 Auth Service (Supabase Auth)

Responsabilité : Authentification et autorisation

  • Inscription / Connexion
  • Gestion des tokens JWT
  • Gestion des rôles (RBAC)
  • OAuth providers (Google, etc.)

Technologies : Supabase Auth (géré)


📚 Catalogue Service

Responsabilité : Gestion du catalogue d'outils

  • CRUD outils
  • Recherche et filtrage
  • Catégories
  • Comparateur de prix

API Endpoints :

  • GET /api/catalogue/outils
  • GET /api/catalogue/outils/:id
  • GET /api/catalogue/categories
  • POST /api/catalogue/search

Base de données : Tables outils, categories, outil_images, prix_comparatifs

Architecture interne (Clean Architecture) :

catalogue-service/
├── domain/
│   ├── entities/
│   │   └── outil.entity.ts
│   └── repositories/
│       └── outil.repository.interface.ts
│
├── application/
│   ├── use-cases/
│   │   ├── get-outils.use-case.ts
│   │   └── search-outils.use-case.ts
│   └── services/
│       └── catalogue.service.ts
│
└── infrastructure/
    ├── repositories/
    │   └── supabase-outil.repository.ts
    └── api/
        └── routes.ts

📅 Reservation Service

Responsabilité : Gestion des réservations

  • Création de réservations
  • Vérification de disponibilité
  • Calcul des prix
  • Gestion du cycle de vie (en cours, terminée, annulée)

API Endpoints :

  • POST /api/reservations
  • GET /api/reservations/:id
  • GET /api/reservations/user/:userId
  • PATCH /api/reservations/:id/cancel
  • GET /api/reservations/check-availability

Base de données : Table reservations

Events émis :

  • reservation.created
  • reservation.confirmed
  • reservation.cancelled

Architecture interne :

reservation-service/
├── domain/
│   ├── entities/
│   │   └── reservation.entity.ts
│   ├── value-objects/
│   │   ├── date-range.vo.ts
│   │   └── price.vo.ts
│   └── rules/
│       └── availability.rule.ts
│
├── application/
│   ├── use-cases/
│   │   ├── create-reservation.use-case.ts
│   │   └── check-availability.use-case.ts
│   └── services/
│       ├── reservation.service.ts
│       └── pricing.service.ts
│
└── infrastructure/
    ├── repositories/
    │   └── supabase-reservation.repository.ts
    └── events/
        └── reservation.events.ts

📦 Inventory Service

Responsabilité : Gestion des stocks temps réel

  • Stocks par entrepôt
  • Disponibilité en temps réel
  • Réservation temporaire de stock
  • Synchronisation multi-entrepôts

API Endpoints :

  • GET /api/inventory/stocks/:outilId
  • GET /api/inventory/entrepots/:entrepotId/stocks
  • POST /api/inventory/reserve
  • POST /api/inventory/release

Base de données : Table stocks, entrepots

Realtime : Supabase Realtime pour push des mises à jour

Events émis/écoutés :

  • Écoute : reservation.confirmed → Décrémente stock
  • Écoute : reservation.cancelled → Libère stock
  • Émet : stock.updated

💳 Payment Service

Responsabilité : Gestion des paiements

  • Intégration Stripe (simulée)
  • Création de payment intent
  • Confirmation de paiement
  • Remboursements

API Endpoints :

  • POST /api/payment/create-intent
  • POST /api/payment/confirm
  • POST /api/payment/refund
  • GET /api/payment/:id/status

Base de données : Table transactions (logs)

Events émis :

  • payment.succeeded
  • payment.failed

💬 Notification Service

Responsabilité : Notifications multi-canal

  • Emails (Resend API)
  • Notifications in-app
  • SMS (futur)

API Endpoints :

  • POST /api/notifications/send
  • GET /api/notifications/user/:userId

Events écoutés :

  • reservation.created → Email confirmation
  • payment.succeeded → Email facture
  • stock.low → Email admin

Communication entre Microservices

1. Communication Synchrone (REST)

Pour les opérations nécessitant une réponse immédiate :

// Reservation Service appelle Inventory Service
const availability = await fetch('/api/inventory/check-availability', {
  method: 'POST',
  body: JSON.stringify({ outilId, entrepotId, dateDebut, dateFin })
})

2. Communication Asynchrone (Events)

Pour les opérations non-bloquantes :

// Reservation Service émet un event
await eventBus.publish('reservation.confirmed', {
  reservationId,
  outilId,
  entrepotId,
  dateDebut,
  dateFin
})

// Inventory Service écoute l'event
eventBus.subscribe('reservation.confirmed', async (data) => {
  await inventoryService.reserveStock(data.outilId, data.entrepotId)
})

Implémentation : Supabase Realtime Channels (Pub/Sub)


API Gateway / BFF (Backend For Frontend)

Le frontend Next.js agit comme BFF (Backend For Frontend) :

// app/api/reservations/route.ts (API Gateway pattern)
export async function POST(request: Request) {
  const data = await request.json()
  
  // 1. Vérifier auth
  const user = await authService.getCurrentUser()
  
  // 2. Vérifier disponibilité (appel Inventory Service)
  const isAvailable = await inventoryService.checkAvailability(data)
  if (!isAvailable) {
    return NextResponse.json({ error: 'Unavailable' }, { status: 400 })
  }
  
  // 3. Créer réservation (appel Reservation Service)
  const reservation = await reservationService.create({
    ...data,
    utilisateurId: user.id
  })
  
  // 4. Créer payment intent (appel Payment Service)
  const paymentIntent = await paymentService.createIntent({
    amount: reservation.prixTotal,
    reservationId: reservation.id
  })
  
  return NextResponse.json({ reservation, paymentIntent })
}

Avantages du BFF :

  • Agrégation de plusieurs microservices
  • Réduction du nombre d'appels frontend
  • Logique d'orchestration centralisée
  • Adaptation au besoin du client (web, mobile)

Architecture Hybride : Couches + Microservices

Chaque microservice implémente Clean Architecture en interne :

┌─────────────────────────────────────────────────────────┐
│                   MICROSERVICE                          │
│  ┌─────────────────────────────────────────────────┐   │
│  │  API Layer (Controllers/Routes)                 │   │
│  └────────────────┬────────────────────────────────┘   │
│                   │                                     │
│  ┌────────────────▼────────────────────────────────┐   │
│  │  Application Layer (Use Cases, Services)        │   │
│  └────────────────┬────────────────────────────────┘   │
│                   │                                     │
│  ┌────────────────▼────────────────────────────────┐   │
│  │  Domain Layer (Entities, Business Rules)        │   │
│  └────────────────┬────────────────────────────────┘   │
│                   │                                     │
│  ┌────────────────▼────────────────────────────────┐   │
│  │  Infrastructure (Repositories, DB, Events)      │   │
│  └─────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────┘

Pourquoi Hybride ?

  • Microservices : Autonomie, scalabilité indépendante, équipes séparées
  • Clean Architecture : Maintenabilité, testabilité, indépendance technologique
  • Best of both worlds

Structure des Dossiers - Architecture Microservices

apps/modern-app/
├── src/
│   ├── app/                        # Next.js App Router (UI + BFF)
│   │   ├── (auth)/
│   │   ├── (main)/
│   │   └── api/                    # API Gateway / BFF
│   │       ├── catalogue/          # Catalogue Service API
│   │       ├── reservations/       # Reservation Service API
│   │       ├── inventory/          # Inventory Service API
│   │       ├── payment/            # Payment Service API
│   │       └── notifications/      # Notification Service API
│   │
│   ├── components/                 # UI Components (Presentation)
│   │
│   ├── services/                   # Microservices (Business Logic)
│   │   ├── catalogue/
│   │   │   ├── domain/
│   │   │   │   ├── entities/
│   │   │   │   └── repositories/
│   │   │   ├── application/
│   │   │   │   ├── use-cases/
│   │   │   │   └── services/
│   │   │   └── infrastructure/
│   │   │       ├── repositories/
│   │   │       └── supabase/
│   │   │
│   │   ├── reservation/
│   │   │   ├── domain/
│   │   │   ├── application/
│   │   │   └── infrastructure/
│   │   │
│   │   ├── inventory/
│   │   │   ├── domain/
│   │   │   ├── application/
│   │   │   └── infrastructure/
│   │   │
│   │   ├── payment/
│   │   │   ├── domain/
│   │   │   ├── application/
│   │   │   └── infrastructure/
│   │   │
│   │   └── notification/
│   │       ├── domain/
│   │       ├── application/
│   │       └── infrastructure/
│   │
│   ├── shared/                     # Code partagé entre services
│   │   ├── domain/                 # Shared entities, value objects
│   │   ├── infrastructure/         # Shared utilities
│   │   └── events/                 # Event Bus
│   │
│   ├── hooks/                      # Custom React Hooks
│   └── types/                      # TypeScript Types

Remarque : Chaque microservice suit Clean Architecture en interne (domain → application → infrastructure)

Flux de Données - Architecture Microservices

sequenceDiagram
    participant U as User (UI)
    participant BFF as API Gateway (BFF)
    participant RS as Reservation Service
    participant IS as Inventory Service
    participant PS as Payment Service
    participant EB as Event Bus
    participant NS as Notification Service
    participant DB as Supabase DB

    U->>BFF: POST /api/reservations
    
    BFF->>IS: Check Availability
    IS->>DB: Query stocks
    DB-->>IS: Stock data
    IS-->>BFF: Available
    
    BFF->>RS: Create Reservation
    RS->>DB: Insert reservation
    DB-->>RS: Reservation created
    RS->>EB: Publish "reservation.created"
    RS-->>BFF: Reservation object
    
    BFF->>PS: Create Payment Intent
    PS-->>BFF: Payment Intent
    
    BFF-->>U: {reservation, paymentIntent}
    
    EB->>IS: Event "reservation.created"
    IS->>DB: Reserve stock
    
    EB->>NS: Event "reservation.created"
    NS->>U: Send confirmation email

📊 Comparaison Architecturale

Styles Architecturaux

Aspect Legacy Moderne
Style Monolithe Microservices Hybride
Frontend MVC (Spring) SPA + BFF (Next.js)
Backend Java EE (monolithe) Microservices (Next.js API)
Communication SOAP REST + Events
Déploiement Tout-en-un Indépendant par service
Scalabilité Verticale Horizontale par service
Architecture interne Aucune Clean Architecture

Caractéristiques

Aspect Legacy Moderne
Couplage Fort (direct DB access) Faible (via interfaces)
Cohésion Faible Forte (services focalisés)
Testabilité Difficile Facile (isolation)
Autonomie Aucune Par service
Résilience Single point of failure Fault isolation
Maintenance Complexe (tout lié) Simple (services isolés)

Décomposition

Critère Legacy Moderne
Granularité 1 application 6 microservices
Base de données 1 monolithe Oracle Database per service (logique)
Déploiement 1 artefact 6 services indépendants
Équipes 1 équipe Possibilité d'équipes par service
Technologies Uniformes Polyglotte (si besoin)

Patterns

Pattern Legacy Moderne
Microservices Non Oui
API Gateway / BFF Non Oui
Event-Driven Non Oui (Supabase Realtime)
Repository Non Oui
Service Layer ⚠️ Partiel Oui
Dependency Injection Non Oui
Clean Architecture Non Oui (par service)
SOLID Non Oui
CQRS Non ⚠️ Partiel (read/write séparés)

Bénéfices de l'Architecture Microservices

Avantages

  1. Autonomie : Chaque service peut être développé/déployé/scalé indépendamment
  2. Isolation des pannes : Un service en panne n'affecte pas les autres
  3. Scalabilité ciblée : Scale uniquement les services sous charge (ex: Inventory Service)
  4. Flexibilité technologique : Possibilité d'utiliser différentes techs par service
  5. Équipes indépendantes : Une équipe par service (futur)
  6. Déploiement continu : Déploiement d'un service sans impact sur les autres

⚠️ Complexités introduites

  1. Distributed system : Gestion de la cohérence, latence réseau
  2. Observabilité : Besoin de tracing distribué (logs, metrics)
  3. Transactions distribuées : Saga pattern (si nécessaire)
  4. Tests d'intégration : Plus complexes (inter-services)

Mitigations appliquées :

  • Event-driven pour asynchrone (résilience)
  • BFF pour agrégation (moins d'appels frontend)
  • Supabase pour gestion centralisée de la data
  • Next.js pour colocalisation (simplicité de déploiement)

Métriques

Métrique Legacy Moderne Impact
Complexité Cyclomatique ~35 ~8 -77%
Couplage (afferent/efferent) ~15 ~3 -80%
Lignes par fichier ~500 ~150 -70%
Tests Coverage 0% 80%+ +∞

🎯 Décisions Techniques

Toutes les décisions architecturales majeures sont documentées dans les ADR.

ADR Principaux

# Titre Décision
001 Structure Mono-repo pnpm workspaces
002 Framework Frontend Next.js 14
003 Backend & BDD Supabase
004 Architecture en Couches Clean Architecture
005 Patterns Repository + Service Layer

🏆 Qualités Architecturales

1. Maintenabilité

Legacy : (1/5)

  • Code spaghetti
  • Pas de séparation des responsabilités
  • Documentation obsolète

Moderne : (5/5)

  • Code modulaire et propre
  • Séparation claire des couches
  • Documentation à jour

2. Testabilité

Legacy : (1/5)

  • Couplage fort = tests difficiles
  • Pas de tests existants
  • Dépendances sur BDD réelle

Moderne : (5/5)

  • Interfaces = mocking facile
  • Tests unitaires isolés
  • Tests d'intégration avec Supabase local

3. Scalabilité

Legacy : (2/5)

  • Scaling vertical uniquement
  • Monolithe = réplication complète
  • Coûts exponentiels

Moderne : (5/5)

  • Scaling horizontal (Vercel)
  • Supabase géré et scalable
  • Coûts linéaires

4. Performance

Legacy : (2/5)

  • Temps de réponse lents
  • Pas de cache efficace
  • Synchronisation différée

Moderne : (5/5)

  • SSR/SSG optimisés
  • Cache intelligent (React Query)
  • Temps réel (Supabase)

5. Sécurité

Legacy : (2/5)

  • Hash MD5 (faible)
  • SQL injection possible
  • Pas de RLS

Moderne : (5/5)

  • JWT sécurisés (Supabase)
  • Parameterized queries
  • Row Level Security (RLS)

📚 Références

Livres

  • Clean Architecture - Robert C. Martin
  • Design Patterns - Gang of Four
  • Domain-Driven Design - Eric Evans

Articles & Ressources


🔄 Évolution

Ce document évolue avec le projet. Chaque décision architecturale majeure doit :

  1. Être discutée en équipe
  2. Faire l'objet d'un ADR
  3. Être intégrée dans ce document
  4. Être reflétée dans le code

Document maintenu par : Équipe Architecture
Dernière révision : Octobre 2025