Facade Pattern: Simplifying Complex Systems

What is the Facade Pattern?
The Facade pattern provides a simplified interface to a complex set of classes, library, or framework. It hides the complexity of subsystems behind a single class that exposes only the operations the client needs.
classDiagram
class Facade {
+operation()
}
class SubsystemA {
+operationA()
}
class SubsystemB {
+operationB()
}
class SubsystemC {
+operationC()
}
class Client
Client --> Facade
Facade --> SubsystemA
Facade --> SubsystemB
Facade --> SubsystemCWhen to Use Facade
- Simplify access to a complex system
- Decouple client code from subsystems
- Provide a unified entry point
- Organize subsystems into layers
The Problem
In an e-commerce system, we have multiple independent services that the client must coordinate:
// Without Facade - Client must coordinate everything manually
const inventory = new InventoryService();
const payment = new PaymentService();
const shipping = new ShippingService();
const notification = new NotificationService();
// Check stock
for (const item of items) {
const inStock = await inventory.checkStock(item.productId, item.quantity);
if (!inStock) throw new Error('Out of stock');
reservations.push(await inventory.reserveStock(item.productId, item.quantity));
}
// Calculate shipping
const shippingOptions = await shipping.calculateShipping(address, items);
const selectedShipping = shippingOptions[0];
// Process payment
const authId = await payment.authorize(total, 'EUR', paymentMethod);
await payment.capture(authId);
// ... more complex steps ...The Solution: Facade
1. Identify the Subsystems
class InventoryService {
async checkStock(productId: string, quantity: number): Promise<boolean> { }
async reserveStock(productId: string, quantity: number): Promise<string> { }
async confirmReservation(reservationId: string): Promise<void> { }
async releaseReservation(reservationId: string): Promise<void> { }
}
class PaymentService {
async authorize(amount: number, currency: string, method: string): Promise<string> { }
async capture(authorizationId: string): Promise<string> { }
async voidAuthorization(authorizationId: string): Promise<void> { }
}
class ShippingService {
async calculateShipping(address: Address, items: OrderItem[]): Promise<ShippingOption[]> { }
async createShipment(orderId: string, address: Address, carrier: string): Promise<string> { }
}
class NotificationService {
async sendOrderConfirmation(email: string, orderId: string): Promise<void> { }
async sendPaymentReceipt(email: string, amount: number): Promise<void> { }
}2. Define the Simplified Interface
The client only needs one operation:
interface OrderRequest {
userId: string;
email: string;
items: Array<{ productId: string; quantity: number; price: number }>;
shippingAddress: Address;
paymentMethod: string;
}
interface OrderResult {
orderId: string;
total: number;
shippingCost: number;
estimatedDelivery: Date;
trackingId?: string;
}3. Implement the Facade
The Facade coordinates all subsystems:
class OrderFacade {
private inventory = new InventoryService();
private payment = new PaymentService();
private shipping = new ShippingService();
private notification = new NotificationService();
async placeOrder(request: OrderRequest): Promise<OrderResult> {
const reservations: string[] = [];
let authorizationId: string | null = null;
try {
// 1. Check stock and reserve
for (const item of request.items) {
const inStock = await this.inventory.checkStock(item.productId, item.quantity);
if (!inStock) {
throw new Error(`Product ${item.productId} out of stock`);
}
const reservationId = await this.inventory.reserveStock(item.productId, item.quantity);
reservations.push(reservationId);
}
// 2. Calculate totals
const subtotal = request.items.reduce((sum, item) => sum + item.price * item.quantity, 0);
const shippingOptions = await this.shipping.calculateShipping(
request.shippingAddress,
request.items
);
const shippingCost = shippingOptions[0].cost;
const total = subtotal + shippingCost;
// 3. Process payment
authorizationId = await this.payment.authorize(total, 'EUR', request.paymentMethod);
await this.payment.capture(authorizationId);
// 4. Confirm reservations
for (const reservationId of reservations) {
await this.inventory.confirmReservation(reservationId);
}
// 5. Create shipment
const orderId = `ORD-${Date.now()}`;
const trackingId = await this.shipping.createShipment(
orderId,
request.shippingAddress,
shippingOptions[0].carrier
);
// 6. Send notifications
await this.notification.sendOrderConfirmation(request.email, orderId);
await this.notification.sendPaymentReceipt(request.email, total);
return {
orderId,
total,
shippingCost,
estimatedDelivery: shippingOptions[0].estimatedDelivery,
trackingId,
};
} catch (error) {
// Rollback on error
for (const reservationId of reservations) {
await this.inventory.releaseReservation(reservationId);
}
if (authorizationId) {
await this.payment.voidAuthorization(authorizationId);
}
throw error;
}
}
}4. Use the Facade
// With Facade - Single call
const facade = new OrderFacade();
const result = await facade.placeOrder({
userId: 'user_123',
email: '[email protected]',
items: [
{ productId: 'PROD_001', quantity: 2, price: 29.99 },
],
shippingAddress: {
street: '123 Main Street',
city: 'Madrid',
country: 'Spain',
postalCode: '28001',
},
paymentMethod: 'card_visa_****1234',
});
console.log(`Order ${result.orderId} completed!`);Benefits of the Facade Pattern
| Benefit | Description |
|---|---|
| Simplicity | Client only knows one interface |
| Decoupling | Changes in subsystems don't affect client |
| Maintainability | Coordination logic centralized |
| Testability | Easy to mock for tests |
Summary
| Aspect | Description |
|---|---|
| Purpose | Simplify access to complex systems |
| Components | Facade, Subsystems, Client |
| Advantage | Decoupling, single entry point |
| Disadvantage | Can become "god object" if it grows too much |
Conclusion
The Facade pattern is essential for simplifying complex systems and creating clean APIs. It's widely used in libraries and frameworks to hide internal complexity.
In the next article we'll explore the State Pattern, which allows changing an object's behavior based on its internal state.
Based on "Design Patterns: Elements of Reusable Object-Oriented Software" (Gang of Four).