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
- Moins de code dupliqué : Plus besoin de répéter le pattern de lazy loading
- Cache automatique : Utilise
WeakMappour un cache efficace - Type-safe : TypeScript garantit la sécurité des types
- Simple à utiliser : API claire et intuitive
- Réutilisable : Un seul
LazyServiceLoaderpeut 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 :
- Ajouter
LazyServiceLoaderdans les imports - Créer une instance dans le constructeur :
this.lazyServices = new LazyServiceLoader(moduleRef); - Remplacer les getters manuels par
this.lazyServices.get(ServiceClass) - Supprimer les propriétés de cache (
private service: Service | null = null;) - Rendre
moduleRefnon 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.tsbackend/src/queue/processors/google-calendar-sync.processor.tsbackend/src/queue/processors/recurring-sessions.processor.tsbackend/src/queue/processors/scheduled-emails.processor.tsbackend/src/queue/processors/workflow-tasks.processor.ts
Controllers
backend/src/quotes-invoices/quotes-invoices.controller.tsbackend/src/quotes-invoices/quotes.controller.tsbackend/src/quotes-invoices/invoices.controller.tsbackend/src/contracts/contracts.controller.tsbackend/src/sessions/sessions.controller.tsbackend/src/storage/storage.controller.ts
Services
backend/src/agent/agent-actions.service.tsbackend/src/agent/agent-data-access.service.tsbackend/src/sessions/sessions.service.tsbackend/src/sessions/session-roadmap.service.tsbackend/src/scheduled-emails/scheduled-emails.service.tsbackend/src/quotes-invoices/quotes-invoices.service.tsbackend/src/contracts/contracts.service.tsbackend/src/contacts/contacts.service.tsbackend/src/users/users.service.tsbackend/src/stripe/stripe.service.tsbackend/src/permissions/permissions.service.ts
Schedulers
backend/src/sessions/recurring-sessions.scheduler.tsbackend/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 deModuleRef.get())getOptional()retournenullsi le service n'est pas disponible (utile pour les services optionnels)