Mini-défi : un conteneur de dépendances
Reproduire le conteneur DI de Nest à la main.
Objectifs
Écrire à la main le mécanisme que NestJS automatise : un registre qui construit, mémorise et injecte vos services.
🔗 Pour vous rafraîchir la mémoire : Pool pg et requêtes paramétrées ($1, $2)
Énoncé
Créez conteneur.js exposant :
const conteneur = creerConteneur();
conteneur.enregistrer("pool", () => new Pool(config));
conteneur.enregistrer("repo", (c) => new UserRepository(c.resoudre("pool")));
conteneur.enregistrer("service", (c) => new UserService(c.resoudre("repo")));
const service = conteneur.resoudre("service");
Contraintes :
- Instanciation paresseuse : rien n'est construit au moment d'
enregistrer— seulement au premierresoudre. - Singleton par défaut : deux
resoudre("service")renvoient LA MÊME instance. - Dépendances transitives : resoudre("service") construit repo, qui construit pool, sans que l'appelant ne s'en soucie.
- Bonus : option
{ singleton: false }pour obtenir une instance neuve à chaque résolution.
Indices
Sur la structure : une Map nom → fabrique + une Map nom → instance déjà créée.
Sur la récursion : la fabrique reçoit le conteneur : elle peut y résoudre ses propres dépendances.
Pour tester le singleton : ajoutez un compteur dans le constructeur du service.
Correction disponibleCherchez d’abord par vous-même.Voir la correction
Correction
Solution complète
export function creerConteneur() {
const fabriques = new Map();
const instances = new Map();
function enregistrer(nom, fabrique, options = {}) {
fabriques.set(nom, { fabrique, singleton: options.singleton !== false });
}
function resoudre(nom) {
const entree = fabriques.get(nom);
if (!entree) throw new Error(`Dépendance inconnue : ${nom}`);
if (entree.singleton && instances.has(nom)) {
return instances.get(nom); // déjà construit : réutilisé
}
const instance = entree.fabrique({ resoudre });
if (entree.singleton) {
instances.set(nom, instance);
}
return instance;
}
return { enregistrer, resoudre };
}
Vérification
let constructions = 0;
class Service {
constructor(repo) {
this.repo = repo;
constructions += 1;
}
}
conteneur.enregistrer("service", (c) => new Service(c.resoudre("repo")));
conteneur.resoudre("service");
conteneur.resoudre("service");
console.log(constructions); // 1 : singleton confirmé
conteneur.enregistrer("transient", () => ({}), { singleton: false });
console.log(conteneur.resoudre("transient") === conteneur.resoudre("transient")); // false
Ce que vous venez de comprendre
Votre conteneur fait exactement ce que fait NestJS :
- Il découvre les dépendances en exécutant vos fabriques (Nest : en lisant les types des constructeurs via reflection).
- Il garantit le singleton par défaut — c'est pourquoi tous vos providers Nest sont partagés.
- Il détecterait les cycles si on lui demandait A → B → A (essayez ! : erreur « Maximum call stack » ; Nest, lui, affiche un message explicite de dépendance circulaire).
La différence n'est plus conceptuelle mais technique : Nest remplace votre déclaration manuelle par des décorateurs et lit les signatures. Vous savez désormais reproduire le mécanisme — le critère « zéro magie » du cursus est rempli pour l'injection de dépendances.