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,
};
}