Aller au contenu principal

Plan de Refactorisation : Élimination des Dépendances Circulaires

Principe Fondamental

Le découpage du backend doit se faire par scope sans dépendances et sans forwardRef.

Chaque module doit :

  • Représenter un domaine métier clair (scope)
  • Avoir des responsabilités bien définies (Single Responsibility Principle)
  • Éviter les dépendances circulaires
  • Ne jamais utiliser forwardRef (signe d'un problème architectural)

Si un forwardRef est nécessaire, cela indique que :

  • Le module a trop de responsabilités (à diviser)
  • Les dépendances sont mal organisées (à réorganiser)
  • L'injection lazy ou les événements doivent être utilisés à la place

Problème Actuel

Le backend contient maintenant 0 occurrences de forwardRef dans les modules (initialement 84 dans 25 modules), ce qui indique une amélioration significative de 84 forwardRef éliminés (100% de réduction). Toutes les dépendances circulaires ont été résolues en utilisant l'injection lazy avec LazyServiceLoader.

✅ Toutes les erreurs de compilation liées aux lazy loaders ont été corrigées. ✅ Tous les fichiers utilisent maintenant LazyServiceLoader pour simplifier le code. ✅ Tous les forwardRef ont été remplacés par des injections lazy.

Progrès de la Refactorisation

✅ Phase 1 - CommunicationModule (COMPLÉTÉ)

  • Créé CommunicationModule avec EmailService et SmsService
  • Supprimé EmailService et SmsService de UsersModule
  • Mis à jour tous les imports (15+ fichiers) pour utiliser CommunicationModule
  • Éliminé plusieurs forwardRef vers UsersModule dans :
    • ClientAccessModule (maintenant utilise CommunicationModule)
    • BackupModule (maintenant utilise CommunicationModule)
    • MigrationsModule (maintenant utilise CommunicationModule)
    • BookingModule (maintenant utilise CommunicationModule)
  • Note : Il reste une dépendance circulaire entre CommunicationModule et UsersModule (via UserValidationService), mais c'est mieux qu'avant car les autres modules n'ont plus besoin de UsersModule pour l'email/SMS

✅ Phase 2 - StorageModule (COMPLÉTÉ)

  • Refactorisé StorageController pour utiliser l'injection lazy avec ModuleRef au lieu d'injecter directement UsersService
  • Supprimé forwardRef de StorageModule vers UsersModule
  • Éliminé plusieurs forwardRef vers StorageModule dans les modules importés après lui dans app.module.ts :
    • ContactFilesModule (ligne 107 vs StorageModule ligne 95)
    • SessionFilesModule (ligne 108 vs StorageModule ligne 95)
    • GdprModule (ligne 97 vs StorageModule ligne 95)
    • BookingModule (ligne 105 vs StorageModule ligne 95)
    • ExportModule (ligne 126 vs StorageModule ligne 95)
    • QueueModule (ligne 127 vs StorageModule ligne 95)
    • SessionsModule (ligne 100 vs StorageModule ligne 95)
    • ContractsModule (ligne 113 vs StorageModule ligne 95)
    • QuotesInvoicesModule (ligne 102 vs StorageModule ligne 95)
    • ScheduledEmailsModule (ligne 122 vs StorageModule ligne 95)
    • AgentModule (ligne 133 vs StorageModule ligne 95)
    • ContractTemplatesModule (ligne 112 vs StorageModule ligne 95)
  • Ajouté forwardRef dans PdfModule vers StorageModule pour résoudre une dépendance circulaire au runtime (nécessaire car PdfService injecte StorageService directement)
  • Résultat : StorageModule n'a plus de forwardRef et de nombreux modules n'ont plus besoin de forwardRef pour l'importer

✅ Phase 3 - SessionsModule (COMPLÉTÉ)

  • Refactorisé SessionsService pour utiliser l'injection lazy avec ModuleRef pour :
    • AnalyticsService (déjà en lazy)
    • ContractsService (déjà en lazy)
    • GoogleCalendarService (converti en lazy)
    • WorkflowTriggersService (déjà en lazy)
  • Retiré AnalyticsModule, ContractsModule, et GoogleCalendarModule des imports de SessionsModule
  • Résultat : SessionsModule a maintenant 8 forwardRef au lieu de 12 (4 supprimés)

✅ Phase 4 - QueueModule (COMPLÉTÉ)

  • Refactorisé tous les processors pour utiliser l'injection lazy avec ModuleRef :
    • ExportProcessor : ExportService et StorageService en lazy
    • RecurringSessionsProcessor : SessionsService en lazy
    • ScheduledEmailsProcessor : ScheduledEmailsService en lazy
    • WorkflowTasksProcessor : WorkflowTasksService en lazy
    • GoogleCalendarSyncProcessor : GoogleCalendarService et SessionsService en lazy
  • Retiré tous les imports de modules métier de QueueModule (ExportModule, GoogleCalendarModule, ScheduledEmailsModule, SessionsModule, WorkflowTasksModule)
  • Résultat : QueueModule n'a plus aucun forwardRef (6 supprimés) - respecte le principe "découpage par scope sans dépendances et forwardRef"

✅ Phase 5 - ScheduledEmailsModule (COMPLÉTÉ)

  • Refactorisé ScheduledEmailsService pour utiliser l'injection lazy avec ModuleRef pour :
    • GmailService (converti en lazy)
    • SessionsService (déjà en lazy)
    • UsersService (converti en lazy)
  • Refactorisé ScheduledEmailsScheduler pour utiliser l'injection lazy avec ModuleRef pour QueueService
  • Retiré tous les imports de modules métier de ScheduledEmailsModule (QueueModule, QuotesInvoicesModule, SessionsModule, UsersModule)
  • Résultat : ScheduledEmailsModule n'a plus aucun forwardRef (4 supprimés) - respecte le principe "découpage par scope sans dépendances et forwardRef"

✅ Phase 6 - QuotesInvoicesModule (COMPLÉTÉ)

  • Refactorisé QuotesInvoicesService pour utiliser l'injection lazy avec ModuleRef pour :
    • AnalyticsService (déjà en lazy)
    • ScheduledEmailsService (converti en lazy)
  • Refactorisé tous les contrôleurs (QuotesInvoicesController, QuotesController, InvoicesController) pour utiliser l'injection lazy avec ModuleRef pour PdfService
  • Retiré tous les imports de modules métier de QuotesInvoicesModule (AnalyticsModule, PdfModule, ScheduledEmailsModule, UsersModule)
  • Résultat : QuotesInvoicesModule n'a plus aucun forwardRef (4 supprimés) - respecte le principe "découpage par scope sans dépendances et forwardRef"

✅ Phase 7 - ContractsModule (COMPLÉTÉ)

  • Refactorisé ContractsService pour utiliser l'injection lazy avec ModuleRef pour :
    • GmailService (converti en lazy)
    • ScheduledEmailsService (déjà en lazy)
  • Refactorisé ContractsController pour utiliser l'injection lazy avec ModuleRef pour PdfService
  • Retiré tous les imports de modules métier de ContractsModule (PdfModule, QuotesInvoicesModule, ScheduledEmailsModule, UsersModule)
  • Résultat : ContractsModule n'a plus aucun forwardRef (4 supprimés) - respecte le principe "découpage par scope sans dépendances et forwardRef"

✅ Phase 8 - SessionsModule (COMPLÉTÉ)

  • Refactorisé RecurringSessionsScheduler pour utiliser l'injection lazy avec ModuleRef pour QueueService
  • Refactorisé SessionRoadmapService pour utiliser l'injection lazy avec ModuleRef pour :
    • PdfService (converti en lazy)
    • SessionChecklistsService (converti en lazy)
  • Refactorisé SessionsController pour utiliser l'injection lazy avec ModuleRef pour WorkflowTasksService
  • Retiré tous les imports de modules métier de SessionsModule (PdfModule, QueueModule, SessionChecklistsModule, SessionFilesModule, UsersModule, WorkflowTasksModule)
  • Résultat : SessionsModule n'a plus aucun forwardRef (6 supprimés) - respecte le principe "découpage par scope sans dépendances et forwardRef"

✅ Phase 9 - AgentModule (COMPLÉTÉ)

  • Refactorisé AgentDataAccessService pour utiliser l'injection lazy avec ModuleRef pour :
    • SearchService (converti en lazy)
    • AnalyticsService (converti en lazy)
  • Refactorisé PdfExportHelper pour utiliser l'injection lazy avec ModuleRef pour PdfService
  • Refactorisé AgentActionsService pour utiliser l'injection lazy avec ModuleRef pour :
    • SearchService (converti en lazy)
    • SessionsService (converti en lazy)
    • UsersService (converti en lazy)
    • StorageService (converti en lazy)
  • Initialisation lazy des handlers dans AgentActionsService pour éviter les dépendances circulaires
  • Refactorisé ExportProcessor pour utiliser l'injection lazy avec ModuleRef pour ExportService et StorageService
  • Retiré tous les imports de modules métier de AgentModule (AnalyticsModule, PdfModule, SearchModule, UsersModule)
  • Résultat : AgentModule n'a plus aucun forwardRef (3 supprimés) - respecte le principe "découpage par scope sans dépendances et forwardRef"

Analyse des Dépendances Circulaires

1. Dépendances liées à StorageModule

Problème : StorageModule importe UsersModule (pour UsersService dans le contrôleur), mais de nombreux modules importent StorageModule.

Solution :

  • Extraire UsersService du contrôleur StorageController
  • Utiliser l'injection lazy ou un service d'abstraction
  • Créer un module StorageCoreModule qui ne dépend pas de UsersModule

2. Dépendances liées à UsersModule

Problème : UsersModule est importé par de nombreux modules, créant des cycles.

Solution :

  • Extraire EmailService et SmsService dans un module CommunicationModule séparé
  • UsersModule devient un module de domaine pur (gestion des utilisateurs uniquement)
  • Les autres modules importent CommunicationModule au lieu de UsersModule

3. Dépendances liées à PdfModule

Problème : PdfModule dépend de StorageModule, et de nombreux modules dépendent de PdfModule.

Solution :

  • PdfModule ne devrait pas dépendre directement de StorageModule
  • Utiliser une interface IStorageService avec injection
  • Ou extraire la logique de stockage dans un service séparé

4. Dépendances liées à QueueModule

Problème : QueueModule importe plusieurs modules qui importent aussi QueueModule.

Solution :

  • Les processors de queue ne devraient pas être dans QueueModule
  • Créer des modules séparés pour chaque processor (ex: ExportProcessorModule)
  • QueueModule devient un module de base sans dépendances métier

5. Dépendances liées à SessionsModule

Problème : SessionsModule importe de nombreux modules qui sont importés après lui dans app.module.ts.

Solution :

  • Réorganiser l'ordre d'import dans app.module.ts
  • Extraire les dépendances non essentielles
  • Utiliser des événements pour la communication asynchrone

Plan de Refactorisation

Phase 1 : Extraction des Services Communs

  1. Créer CommunicationModule

    • Extraire EmailService et SmsService de UsersModule
    • Tous les modules qui ont besoin d'email/SMS importent CommunicationModule
    • UsersModule n'exporte plus que les services liés aux utilisateurs
  2. Créer StorageCoreModule

    • Extraire StorageService sans dépendance à UsersModule
    • StorageController peut rester dans StorageModule mais utilise l'injection lazy

Phase 2 : Réorganisation des Modules

  1. Réorganiser app.module.ts

    • Importer les modules de base en premier (DbModule, LoggerModule, etc.)
    • Importer les modules de services communs ensuite (CommunicationModule, StorageCoreModule)
    • Importer les modules métier en dernier
  2. Séparer les Processors de Queue

    • Créer ExportProcessorModule, ScheduledEmailsProcessorModule, etc.
    • Chaque processor module importe uniquement ce dont il a besoin
    • QueueModule devient un module de base sans dépendances métier

Phase 3 : Utilisation d'Interfaces et d'Abstractions

  1. Créer des interfaces pour les services partagés

    • IStorageService pour le stockage
    • IEmailService pour l'email
    • Utiliser l'injection par token au lieu de classe
  2. Utiliser l'injection lazy

    • Pour les dépendances non critiques
    • Utiliser ModuleRef pour obtenir les services à la demande

Phase 4 : Communication par Événements

  1. Créer un système d'événements
    • Pour les interactions asynchrones entre modules
    • Utiliser @nestjs/event-emitter ou un système custom
    • Éviter les dépendances directes pour les notifications

Modules à Refactoriser en Priorité

  1. StorageModule - Dépendance à UsersModule dans le contrôleur
  2. UsersModule - Trop de responsabilités (email, SMS, utilisateurs)
  3. QueueModule - Contient trop de processors avec dépendances
  4. SessionsModule - Trop de dépendances directes
  5. PdfModule - Dépendance à StorageModule

Ordre d'Exécution Recommandé

  1. Extraire CommunicationModule (EmailService, SmsService)
  2. Refactoriser StorageModule pour supprimer la dépendance à UsersModule
  3. Réorganiser app.module.ts avec le nouvel ordre
  4. Séparer les processors de queue
  5. Utiliser des interfaces pour les services partagés
  6. Implémenter la communication par événements si nécessaire

Métriques de Succès

  • 0 forwardRef dans le codebase
  • Ordre d'import logique dans app.module.ts
  • Modules avec responsabilités claires (Single Responsibility Principle)
  • Pas de dépendances circulaires détectées par l'analyse statique
  • Tous les tests passent après la refactorisation

Notes Importantes

  • Cette refactorisation doit être faite progressivement
  • Tester après chaque phase
  • Documenter les changements dans les README des modules
  • Mettre à jour la documentation d'architecture

Utilitaire LazyServiceLoader

Pour simplifier le code dupliqué des lazy loaders, un utilitaire LazyServiceLoader a été créé dans backend/src/common/service.utils.ts. Tous les fichiers ont été refactorisés pour utiliser cet utilitaire, réduisant significativement le code dupliqué.

✅ Tous les forwardRef ont été remplacés par des injections lazy utilisant LazyServiceLoader.

Avant :

private searchService: SearchService | null = null;

private getSearchService(): SearchService {
if (!this.searchService) {
this.searchService = this.moduleRef.get(SearchService, { strict: false });
}
return this.searchService!;
}

Après :

private readonly lazyServices: LazyServiceLoader;

constructor(moduleRef: ModuleRef) {
this.lazyServices = new LazyServiceLoader(moduleRef);
}

private getSearchService(): SearchService {
return this.lazyServices.get(SearchService);
}

Voir LAZY_SERVICE_LOADER.md pour plus de détails.