Aller au contenu principal

Concept #025

Design Patterns

Le Design Pattern Facade

#0256 min de lecture
  • design pattern
  • structurel
  • abstraction
Facade : une interface simple devant la complexité

Le Design Pattern Facade en une phrase

Facade est un patron de conception structurel qui fournit une interface simplifiée à un ensemble complexe de classes, de librairies ou de sous-systèmes. Le code client interagit avec la Facade au lieu de manipuler directement les composants internes.

Votre code "client" ne devrait pas avoir à entrer dans la "cuisine" (votre logique métier complexe) pour hurler des ordres aux différents "chefs" (les sous-systèmes). C'est le chaos assuré et une dépendance forte à la structure interne de la cuisine. La Facade, c'est le serveur qui prend votre commande et se charge de coordonner tout le reste.


Le problème : le couplage au sous-système

Imaginez un système de e-commerce. Pour traiter une commande, il faut coordonner plusieurs sous-systèmes : vérifier le stock, calculer les taxes, traiter le paiement, générer la facture, envoyer un email de confirmation et mettre à jour les statistiques.

Sans Facade, le code client doit connaître et orchestrer tous ces sous-systèmes :

// Le code client connaît TOUS les sous-systèmes
const inventory = new InventoryService();
const taxCalculator = new TaxCalculator();
const paymentGateway = new PaymentGateway();
const invoiceGenerator = new InvoiceGenerator();
const emailService = new EmailService();
const analyticsTracker = new AnalyticsTracker();
 
// Et il doit les orchestrer dans le bon ordre
const stockOk = inventory.checkAvailability(order.items);
if (!stockOk) throw new Error("Rupture de stock");
 
inventory.reserve(order.items);
 
const taxes = taxCalculator.calculate(order.items, order.shippingAddress);
const total = order.subtotal + taxes;
 
const payment = await paymentGateway.charge(order.customerId, total);
if (!payment.success) {
  inventory.release(order.items); // rollback !
  throw new Error("Paiement refusé");
}
 
inventory.confirm(order.items);
const invoice = invoiceGenerator.generate(order, taxes, payment);
await emailService.sendOrderConfirmation(order.customerEmail, invoice);
analyticsTracker.trackPurchase(order, total);

Ce code a plusieurs problèmes :

  • Le client est fortement couplé à six classes différentes.
  • L'ordre d'appel est critique et dupliqué partout où on traite une commande.
  • Si un sous-système change (nouvelle API de paiement, nouveau calcul de taxe), tout le code client doit être modifié.
  • La logique de rollback (libérer le stock si le paiement échoue) est à la charge du client.

La solution : la Facade

La Facade encapsule toute cette complexité derrière une interface simple :

class OrderFacade {
  constructor(
    private readonly inventory: InventoryService,
    private readonly taxCalculator: TaxCalculator,
    private readonly paymentGateway: PaymentGateway,
    private readonly invoiceGenerator: InvoiceGenerator,
    private readonly emailService: EmailService,
    private readonly analyticsTracker: AnalyticsTracker,
  ) {}
 
  async placeOrder(order: Order): Promise<OrderResult> {
    // 1. Vérifier et réserver le stock
    const stockOk = this.inventory.checkAvailability(order.items);
    if (!stockOk) {
      return { success: false, error: "Rupture de stock" };
    }
    this.inventory.reserve(order.items);
 
    // 2. Calculer les taxes
    const taxes = this.taxCalculator.calculate(
      order.items,
      order.shippingAddress,
    );
    const total = order.subtotal + taxes;
 
    // 3. Traiter le paiement
    const payment = await this.paymentGateway.charge(
      order.customerId,
      total,
    );
    if (!payment.success) {
      this.inventory.release(order.items);
      return { success: false, error: "Paiement refusé" };
    }
 
    // 4. Confirmer et finaliser
    this.inventory.confirm(order.items);
    const invoice = this.invoiceGenerator.generate(order, taxes, payment);
    await this.emailService.sendOrderConfirmation(
      order.customerEmail,
      invoice,
    );
    this.analyticsTracker.trackPurchase(order, total);
 
    return { success: true, invoice, paymentId: payment.id };
  }
}

Le code client devient :

const orderFacade = new OrderFacade(/* ... dépendances injectées ... */);
 
// Une seule méthode, une seule responsabilité pour le client
const result = await orderFacade.placeOrder(order);
 
if (!result.success) {
  console.log(`Commande échouée : ${result.error}`);
}

Le client ne connaît qu'une seule classe avec une seule méthode. L'orchestration, le rollback, l'ordre d'exécution -- tout est masqué derrière la Facade.


La Facade ne cache pas, elle simplifie

Un point important : la Facade n'interdit pas l'accès aux sous-systèmes. Elle offre un raccourci pour les cas d'usage courants.

// Cas courant : utiliser la Facade
await orderFacade.placeOrder(order);
 
// Cas spécifique : accéder directement au sous-système
// (par exemple, un admin qui vérifie le stock sans passer de commande)
const stock = inventory.checkAvailability(items);

C'est la différence avec un anti-pattern qui masquerait totalement les sous-systèmes. La Facade est une option pratique, pas une prison.


Exemples dans la vie réelle

La Facade est partout, souvent sans qu'on la nomme explicitement.

Les ORM comme Facades de bases de données

// Sans Facade : SQL brut avec gestion de connexion
const connection = await pool.getConnection();
const [rows] = await connection.query(
  "SELECT * FROM users WHERE email = ? AND active = 1",
  [email],
);
connection.release();
const user = rows[0];
 
// Avec la Facade de l'ORM (Prisma)
const user = await prisma.user.findFirst({
  where: { email, active: true },
});

L'ORM masque la gestion des connexions, la construction SQL, le mapping des types et la gestion des transactions.

Les SDK cloud

// Sans Facade : API HTTP brute AWS S3
const signature = computeAWSSignature(/* ... 15 paramètres ... */);
const response = await fetch(
  `https://my-bucket.s3.amazonaws.com/${key}`,
  {
    headers: {
      Authorization: signature,
      "x-amz-date": amzDate,
      "x-amz-content-sha256": contentHash,
    },
  },
);
 
// Avec la Facade du SDK
const s3 = new S3Client({ region: "eu-west-1" });
await s3.send(new PutObjectCommand({
  Bucket: "my-bucket",
  Key: key,
  Body: content,
}));

Le SDK AWS est une Facade massive qui abstrait la signature des requêtes, la gestion des retries, la sérialisation et le parsing des réponses XML.


Facade vs Adapter : la distinction

Les deux patterns simplifient une interface, mais leur intention diffère :

FacadeAdapter
ObjectifSimplifier un ensemble de sous-systèmesRendre compatible une interface existante
PortéePlusieurs classes/systèmesUne seule classe ou interface
InterfaceNouvelle interface simplifiéeConversion vers une interface attendue
MotivationRéduire la complexitéRésoudre une incompatibilité

L'Adapter traduit. La Facade simplifie.


Quand utiliser la Facade ?

  • Vous orchestrez plusieurs sous-systèmes dans un ordre précis, avec de la gestion d'erreur entre les étapes. Centralisez cette logique dans une Facade.
  • Vous intégrez une librairie tierce complexe dont vous n'utilisez qu'une fraction. Une Facade isole votre code des détails de la librairie.
  • Vous voulez découpler les couches. Les contrôleurs HTTP n'ont pas besoin de connaître les détails des services métier. Une Facade applicative sert d'intermédiaire.
  • Le code client est dupliqué. Si la même séquence d'appels à plusieurs services apparaît à trois endroits différents, c'est une Facade qui s'ignore.

Quand ne pas utiliser la Facade ?

  • Pour un seul sous-système simple. Si l'interface est déjà claire, ajouter une Facade ne fait qu'ajouter une indirection inutile.
  • Si la Facade devient un "God Object". Une Facade qui grossit indéfiniment et expose des dizaines de méthodes perd son intérêt. Découpez en plusieurs Facades spécialisées.
  • Si elle masque des décisions que le client devrait prendre. La Facade simplifie les cas courants mais ne doit pas empêcher le client de comprendre ce qui se passe quand il en a besoin.

Résumé

La Facade est l'un des patterns les plus intuitifs et les plus utiles. Elle place une interface simple devant un système complexe, réduisant le couplage et centralisant l'orchestration. Le code client n'a pas besoin de connaître les rouages internes -- il passe sa commande, et la Facade se charge de coordonner la cuisine.