Concept #025
Design PatternsLe Design Pattern Facade
- design pattern
- structurel
- abstraction
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 :
| Facade | Adapter | |
|---|---|---|
| Objectif | Simplifier un ensemble de sous-systèmes | Rendre compatible une interface existante |
| Portée | Plusieurs classes/systèmes | Une seule classe ou interface |
| Interface | Nouvelle interface simplifiée | Conversion vers une interface attendue |
| Motivation | Ré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.