Introduction : Pourquoi votre code a besoin de Dependency Injection ?
Chaque développeur JavaScript rencontre tôt ou tard le problème du code "fortement couplé" (tightly coupled). Imaginez une classe UserService qui crée en interne une instance de Database et de Logger. Que se passera-t-il lorsque vous voudrez tester UserService sans base de données réelle ? Ou remplacer Logger par une autre implémentation ? Dans le meilleur des cas, vous réécrirez toute la classe ; dans le pire, vous casserez toute l'application.
La solution à ce problème est le Dependency Injection (DI), ou injection de dépendances. C'est l'un des principes de conception clés qui sous-tend le concept d'Inversion of Control (IoC). Dans cet article, nous allons voir ce qu'est le DI, comment l'implémenter en JavaScript pur (Node.js) sans utiliser de frameworks, et pourquoi il est crucial pour créer des applications évolutives et testables.
Qu'est-ce que Dependency Injection en termes simples ?
Imaginez que vous construisez une maison. Au lieu de sceller les prises électriques dans les murs pour toujours, vous installez des prises standard. Aujourd'hui, vous y branchez une vieille lampe, demain, un luminaire intelligent. Vous ne changez pas les murs — vous changez les dépendances.
En programmation, le Dependency Injection est un pattern dans lequel un objet reçoit ses dépendances (autres objets, services, configurations) de l'extérieur, plutôt que de les créer lui-même. Au lieu d'écrire :
// ❌ Mauvais : couplage fortclass UserService { constructor() { this.db = new Database('mongodb://...'); this.logger = new ConsoleLogger(); }}Nous écrivons :
// ✅ Bon : injection de dépendancesclass UserService { constructor(database, logger) { this.db = database; this.logger = logger; }}Maintenant, UserService ne sait pas comment Database et Logger sont créés. Sa tâche est seulement de les utiliser. C'est ce qu'on appelle l'inversion de contrôle : le contrôle de la création des dépendances passe de la classe au code appelant.
Types d'injection de dépendances
Il existe trois principales façons d'injecter :
- Par le constructeur (Constructor Injection) — la méthode la plus courante et préférée. Les dépendances sont transmises lors de la création de l'objet.
- Par les propriétés (Property Injection) — les dépendances sont définies après la création de l'objet. Pratique pour les dépendances optionnelles.
- Par les méthodes (Method Injection) — la dépendance est transmise à une méthode spécifique lorsqu'elle n'est nécessaire que pour une seule action.
Écrire un simple conteneur DI sur Node.js
Dans les grands projets, gérer les dépendances manuellement devient complexe. C'est là qu'intervient le conteneur DI — un objet qui sait comment créer et stocker toutes les dépendances de votre application. Écrivons un conteneur simple mais fonctionnel en JavaScript pur.
Notre conteneur prendra en charge deux modes : singleton (une seule instance pour toute l'application) et transient (une nouvelle instance à chaque demande).
// di-container.jsclass DIContainer { constructor() { this.registry = new Map(); this.instances = new Map(); }
// Enregistre une dépendance avec une fonction factory register(name, factory, { singleton = true } = {}) { this.registry.set(name, { factory, singleton }); if (singleton) { this.instances.delete(name); // Réinitialise le cache lors de la réinscription } }
// Obtient une instance de la dépendance resolve(name) { const item = this.registry.get(name); if (!item) { throw new Error(`Dépendance "${name}" non trouvée`); }}