Container d'injection de dépendances
Comprendre le système d'injection de dépendances avec les DI containers client et server dans Foundation Builder
Injection de dépendances
L'injection de dépendances (DI) est un pattern fondamental de l'architecture hexagonale qui permet de découpler les composants et de faciliter les tests. Foundation Builder implémente un système de DI avec des conteneurs séparés pour les environnements client et server.
Principe de l'injection de dépendances
Inversion de contrôle
L'injection de dépendances inverse le contrôle de la création des objets. Au lieu que les classes créent leurs propres dépendances, elles les reçoivent via leur constructeur ou des méthodes d'injection.
Avantages de la DI
- Découplage : Les composants ne dépendent que des interfaces, pas des implémentations
- Testabilité : Facilite l'utilisation de mocks pour les tests
- Flexibilité : Permet de changer d'implémentation sans modifier le code métier
- Maintenabilité : Centralise la configuration des dépendances
Configuration des dépendances
Ordre d'initialisation
- Repositories : Les repositories sont créés en premier
- Services : Les services sont instanciés avec l’injection de leurs dépendances, qui sont des repositories.
- Enregistrement : Les services sont enregistrés dans le conteneur
Gestion des erreurs
Le système de DI gère les erreurs de configuration :
- Service non trouvé : Lance une erreur explicite si un service n'est pas enregistré
- Type safety : TypeScript garantit la cohérence des types
- Validation : Vérification de l'existence des services au runtime
DI Container Client
Le conteneur client gère les services et repositories optimisés pour l'environnement frontend.
Structure du DI Container Client
export type ClientServiceMap = {
AuthService: AuthPortIn;
UserService: UserPortIn;
};
class ClientDIContainer {
private services: Map<keyof ClientServiceMap, ClientServiceMap[keyof ClientServiceMap]> = new Map();
constructor() {
this.initializeClientServices();
}
private initializeClientServices() {
// Repositories
const authRepository = new BetterAuthRepositoryImpl();
const userRepository = new FirestoreUserRepositoryImpl();
// Services
const authService = new AuthService(authRepository);
const userService = new UserService(userRepository);
this.services.set("AuthService", authService);
this.services.set("UserService", userService);
}
public get<K extends keyof ClientServiceMap>(serviceName: K): ClientServiceMap[K] {
const service = this.services.get(serviceName);
if (!service) {
throw new Error(`Client service ${serviceName} not found`);
}
return service as ClientServiceMap[K];
}
}DI Container Server
Le conteneur server gère les services et repositories optimisés pour l'environnement backend.
Structure du DI Container Server
export type ServiceMap = {
AuthService: AuthPortIn;
PaymentService: PaymentPortIn;
EmailService: EmailPortIn;
};
class DIContainer {
private services: Map<keyof ServiceMap, ServiceMap[keyof ServiceMap]> = new Map();
constructor() {
this.initializeServices();
}
private initializeServices() {
// Repositories
const paymentRepository = new StripePaymentRepositoryImpl();
const emailRepository = new ResendEmailRepositoryImpl();
const authRepository = new BetterAuthRepositoryImpl();
// Services
const paymentService = new PaymentService(paymentRepository);
const emailService = new EmailService(emailRepository);
const authService = new AuthService(authRepository);
this.services.set("PaymentService", paymentService);
this.services.set("EmailService", emailService);
this.services.set("AuthService", authService);
}
public get<K extends keyof ServiceMap>(serviceName: K): ServiceMap[K] {
const service = this.services.get(serviceName);
if (!service) {
throw new Error(`Service ${serviceName} not found`);
}
return service as ServiceMap[K];
}
}Utilisation des DI Containers
Instanciation des services
Les DI containers sont instanciés automatiquement et exportent des instances prêtes à l'emploi :
// Côté client
const diContainerClient = new ClientDIContainer();
export const authServiceInstance: AuthPortIn = diContainerClient.get("AuthService");
export const userServiceInstance: UserPortIn = diContainerClient.get("UserService");
// Côté server
const diContainerServer = new DIContainer();
export const paymentServiceInstance: PaymentPortIn = diContainerServer.get("PaymentService");
export const emailServiceInstance: EmailPortIn = diContainerServer.get("EmailService");
export const authServiceInstance: AuthPortIn = diContainerServer.get("AuthService");Récupération des services
// Dans un composant React (côté client)
import { authServiceInstance } from "@/core/client/di-container-client";
const handleLogin = async (email: string, password: string) => {
const user = await authServiceInstance.loginWithEmail(email, password);
// ...
};
// Dans une API route (côté server)
import { paymentServiceInstance } from "@/core/server/di-container-server";
export async function POST(request: Request) {
const paymentIntent = await paymentServiceInstance.createPaymentIntent(data);
// ...
}