Design Pattern
Structurels

Facade

Fournir une interface simplifiée à un sous-système complexe

Problème

Un sous-système complexe (plusieurs APIs, services, transformations) expose une interface difficile à utiliser directement. La Facade fournit une interface de haut niveau qui simplifie les interactions.

Exemple : Facade d'un sous-système e-commerce

// Sous-système complexe : plusieurs services distincts
class InventoryService {
  async checkStock(productId: string): Promise<number> { /* ... */ }
  async reserveStock(productId: string, quantity: number): Promise<void> { /* ... */ }
  async releaseStock(productId: string, quantity: number): Promise<void> { /* ... */ }
}

class PricingService {
  async getPrice(productId: string): Promise<number> { /* ... */ }
  async applyDiscount(price: number, couponCode: string): Promise<number> { /* ... */ }
  async calculateTax(price: number, region: string): Promise<number> { /* ... */ }
}

class PaymentService {
  async createPaymentIntent(amount: number, currency: string): Promise<string> { /* ... */ }
  async confirmPayment(intentId: string, paymentMethodId: string): Promise<boolean> { /* ... */ }
  async refund(paymentIntentId: string): Promise<void> { /* ... */ }
}

class OrderService {
  async createOrder(items: OrderItem[], userId: string): Promise<Order> { /* ... */ }
  async updateOrderStatus(orderId: string, status: OrderStatus): Promise<void> { /* ... */ }
}

class NotificationService {
  async sendOrderConfirmation(orderId: string, userEmail: string): Promise<void> { /* ... */ }
}
// La Facade : une interface simple pour le processus de checkout
class CheckoutFacade {
  constructor(
    private inventory: InventoryService,
    private pricing: PricingService,
    private payment: PaymentService,
    private orders: OrderService,
    private notifications: NotificationService,
  ) {}

  async checkout(params: {
    items: { productId: string; quantity: number }[];
    userId: string;
    userEmail: string;
    paymentMethodId: string;
    couponCode?: string;
    region: string;
  }): Promise<{ orderId: string; total: number }> {
    // 1. Vérifier les stocks
    for (const item of params.items) {
      const stock = await this.inventory.checkStock(item.productId);
      if (stock < item.quantity) throw new Error(`Insufficient stock for ${item.productId}`);
    }

    // 2. Calculer le total
    let total = 0;
    const orderItems: OrderItem[] = [];
    for (const item of params.items) {
      let price = await this.pricing.getPrice(item.productId);
      if (params.couponCode) price = await this.pricing.applyDiscount(price, params.couponCode);
      const tax = await this.pricing.calculateTax(price, params.region);
      total += (price + tax) * item.quantity;
      orderItems.push({ productId: item.productId, quantity: item.quantity, price: price + tax });
    }

    // 3. Réserver les stocks
    for (const item of params.items) {
      await this.inventory.reserveStock(item.productId, item.quantity);
    }

    // 4. Payer
    const intentId = await this.payment.createPaymentIntent(total, 'EUR');
    const paid = await this.payment.confirmPayment(intentId, params.paymentMethodId);
    if (!paid) {
      for (const item of params.items) {
        await this.inventory.releaseStock(item.productId, item.quantity);
      }
      throw new Error('Payment failed');
    }

    // 5. Créer la commande et notifier
    const order = await this.orders.createOrder(orderItems, params.userId);
    await this.notifications.sendOrderConfirmation(order.id, params.userEmail);

    return { orderId: order.id, total };
  }
}

// À l'usage : une seule ligne d'appel
const { orderId, total } = await checkoutFacade.checkout({
  items: [{ productId: 'p1', quantity: 2 }],
  userId: 'u1',
  userEmail: 'user@example.com',
  paymentMethodId: 'pm_xxx',
  couponCode: 'SUMMER10',
  region: 'FR',
});

Facade vs Service Layer

La Facade est souvent confondue avec un service. La distinction :

  • Service : encapsule la logique métier d'un domaine (UserService, OrderService)
  • Facade : orchestre plusieurs services pour une opération de haut niveau

La Facade ne contient pas de logique métier propre, elle délègue aux services sous-jacents.

En React : custom hooks comme Facade

// Facade hook : orchestre query + mutation + side effects
function useCheckout() {
  const cart = useCartStore();
  const { user } = useAuth();

  const mutation = useMutation({
    mutationFn: (params: CheckoutParams) => checkoutFacade.checkout(params),
    onSuccess: ({ orderId }) => {
      cart.clear();
      router.push(`/orders/${orderId}`);
    },
    onError: (error) => {
      toast.error(error.message);
    },
  });

  return {
    checkout: (paymentMethodId: string) =>
      mutation.mutate({
        items: cart.items.map(i => ({ productId: i.id, quantity: i.quantity })),
        userId: user.id,
        userEmail: user.email,
        paymentMethodId,
        region: user.region,
      }),
    isProcessing: mutation.isPending,
    error: mutation.error,
  };
}