Aller au contenu principal

Lazy Service Loader - Utilitaire pour éviter les dépendances circulaires

Vue d'ensemble

L'utilitaire LazyServiceLoader simplifie le pattern de lazy loading des services pour éviter les dépendances circulaires dans NestJS. Il remplace le code dupliqué répétitif par une solution élégante et réutilisable.

Problème résolu

Avant, chaque service devait implémenter manuellement le pattern de lazy loading :

private searchService: SearchService | null = null;

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

Ce pattern était répété dans de nombreux fichiers, créant beaucoup de code dupliqué.

Solution : LazyServiceLoader

Utilisation de base

import { LazyServiceLoader } from "../common/index.js";

export class MyService {
private readonly lazyServices: LazyServiceLoader;

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

someMethod() {
const searchService = this.lazyServices.get(SearchService);
// Utiliser searchService...
}
}

Services optionnels

Pour les services qui peuvent ne pas être disponibles (retourne null si absent) :

private getAnalyticsService(): AnalyticsService | null {
return this.lazyServices.getOptional(AnalyticsService);
}

Exemple complet

Avant :

export class AgentActionsService {
private searchService: SearchService | null = null;
private sessionsService: SessionsService | null = null;
private usersService: UsersService | null = null;

constructor(private readonly moduleRef: ModuleRef) {}

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

private getSessionsService(): SessionsService {
if (!this.sessionsService) {
this.sessionsService = this.moduleRef.get(SessionsService, {
strict: false,
});
}
return this.sessionsService!;
}

private getUsersService(): UsersService {
if (!this.usersService) {
this.usersService = this.moduleRef.get(UsersService, { strict: false });
}
return this.usersService!;
}
}

Après :

import { LazyServiceLoader } from "../common/index.js";

export class AgentActionsService {
private readonly lazyServices: LazyServiceLoader;

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

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

private getSessionsService(): SessionsService {
return this.lazyServices.get(SessionsService);
}

private getUsersService(): UsersService {
return this.lazyServices.get(UsersService);
}
}

Avantages

  1. Moins de code dupliqué : Plus besoin de répéter le pattern de lazy loading
  2. Cache automatique : Utilise WeakMap pour un cache efficace
  3. Type-safe : TypeScript garantit la sécurité des types
  4. Simple à utiliser : API claire et intuitive
  5. Réutilisable : Un seul LazyServiceLoader peut gérer tous les services

API

get<T>(serviceClass: Type<T>): T

Récupère un service de manière lazy, le met en cache pour les appels suivants. Lance une exception si le service n'est pas disponible.

getOptional<T>(serviceClass: Type<T>): T | null

Récupère un service de manière lazy, retourne null si le service n'est pas disponible (au lieu de lancer une exception).

Migration

Pour migrer un service existant :

  1. Ajouter LazyServiceLoader dans les imports
  2. Créer une instance dans le constructeur : this.lazyServices = new LazyServiceLoader(moduleRef);
  3. Remplacer les getters manuels par this.lazyServices.get(ServiceClass)
  4. Supprimer les propriétés de cache (private service: Service | null = null;)
  5. Rendre moduleRef non privé (ou le supprimer si plus utilisé ailleurs)

Fichiers utilisant LazyServiceLoader

Tous les fichiers avec lazy loading ont été refactorisés pour utiliser LazyServiceLoader :

Processeurs de queue

  • backend/src/queue/processors/export.processor.ts
  • backend/src/queue/processors/google-calendar-sync.processor.ts
  • backend/src/queue/processors/recurring-sessions.processor.ts
  • backend/src/queue/processors/scheduled-emails.processor.ts
  • backend/src/queue/processors/workflow-tasks.processor.ts

Controllers

  • backend/src/quotes-invoices/quotes-invoices.controller.ts
  • backend/src/quotes-invoices/quotes.controller.ts
  • backend/src/quotes-invoices/invoices.controller.ts
  • backend/src/contracts/contracts.controller.ts
  • backend/src/sessions/sessions.controller.ts
  • backend/src/storage/storage.controller.ts

Services

  • backend/src/agent/agent-actions.service.ts
  • backend/src/agent/agent-data-access.service.ts
  • backend/src/sessions/sessions.service.ts
  • backend/src/sessions/session-roadmap.service.ts
  • backend/src/scheduled-emails/scheduled-emails.service.ts
  • backend/src/quotes-invoices/quotes-invoices.service.ts
  • backend/src/contracts/contracts.service.ts
  • backend/src/contacts/contacts.service.ts
  • backend/src/users/users.service.ts
  • backend/src/stripe/stripe.service.ts
  • backend/src/permissions/permissions.service.ts

Schedulers

  • backend/src/sessions/recurring-sessions.scheduler.ts
  • backend/src/scheduled-emails/scheduled-emails.scheduler.ts

Helpers

  • backend/src/agent/helpers/export/pdf-export.helper.ts

Notes

  • Le cache utilise WeakMap, donc il est automatiquement nettoyé quand les services ne sont plus référencés
  • get() lance une exception si le service n'est pas disponible (comportement par défaut de ModuleRef.get())
  • getOptional() retourne null si le service n'est pas disponible (utile pour les services optionnels)