Foundation Builder Docs
Architecture

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

  1. Repositories : Les repositories sont créés en premier
  2. Services : Les services sont instanciés avec l’injection de leurs dépendances, qui sont des repositories.
  3. 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);
  // ...
}