The Facade Pattern is a structural design pattern that provides a simplified, unified interface to a complex subsystem. Think of ordering food at a restaurant — you simply tell the waiter what you want. Behind the scenes, the kitchen, inventory, and billing systems coordinate to fulfill your order. The waiter is the facade.
In software, complex operations often involve orchestrating multiple services, classes, or APIs. A facade wraps this complexity behind a single, clean method call that clients can use without understanding the internals.
Processing an e-commerce order involves multiple subsystems: validating inventory, charging the payment method, generating an invoice, sending a confirmation email, and updating the shipping system. Each subsystem has its own interface, configuration, and error handling requirements.
Forcing client code to interact with all these subsystems directly creates tight coupling and duplicates complex orchestration logic everywhere an order is placed — from the web controller, the mobile API, the admin panel, and the batch processing system.
Create an OrderFacade class that exposes a single placeOrder() method. Internally, it coordinates all the subsystems in the correct order, handles errors, and returns a clean result. Client code only interacts with the facade, completely shielded from the complexity underneath.
If any subsystem changes its interface or a new step is added to the process, only the facade needs to be updated — not every caller.
The Facade Pattern applies the Principle of Least Knowledge (Law of Demeter) — a client should only talk to its immediate friends, not the internal components of those friends. It reduces coupling between the client and the subsystem.
Here we simplify order processing by wrapping multiple subsystems behind a single facade.
// Subsystem: Inventory
public class InventoryService {
public boolean checkStock(String productId, int quantity) {
System.out.println("[Inventory] Checking stock for " + productId);
return true; // Simulate available stock
}
public void reserveStock(String productId, int quantity) {
System.out.println("[Inventory] Reserved " + quantity + " units of " + productId);
}
}
// Subsystem: Payment
public class PaymentService {
public String charge(String customerId, double amount) {
System.out.println("[Payment] Charging $" + amount + " to customer " + customerId);
return "TXN-" + System.currentTimeMillis();
}
public void refund(String transactionId) {
System.out.println("[Payment] Refunding transaction " + transactionId);
}
}
// Subsystem: Invoice
public class InvoiceService {
public String generateInvoice(String orderId, String customerId, double amount) {
String invoiceId = "INV-" + System.currentTimeMillis();
System.out.println("[Invoice] Generated " + invoiceId + " for order " + orderId);
return invoiceId;
}
}
// Subsystem: Email
public class EmailService {
public void sendOrderConfirmation(String customerId, String orderId) {
System.out.println("[Email] Confirmation sent to customer " + customerId + " for order " + orderId);
}
}
// Subsystem: Shipping
public class ShippingService {
public String createShipment(String orderId, String address) {
String trackingId = "SHIP-" + System.currentTimeMillis();
System.out.println("[Shipping] Created shipment " + trackingId + " to " + address);
return trackingId;
}
}
// Result object
public class OrderResult {
private String orderId;
private String transactionId;
private String trackingId;
private boolean success;
private String message;
// Constructor, getters omitted for brevity
public OrderResult(String orderId, String transactionId, String trackingId,
boolean success, String message) {
this.orderId = orderId;
this.transactionId = transactionId;
this.trackingId = trackingId;
this.success = success;
this.message = message;
}
@Override
public String toString() {
return String.format("Order %s | Txn: %s | Tracking: %s | %s",
orderId, transactionId, trackingId, message);
}
}
// FACADE: Simplifies the entire order process
public class OrderFacade {
private final InventoryService inventory;
private final PaymentService payment;
private final InvoiceService invoice;
private final EmailService email;
private final ShippingService shipping;
public OrderFacade() {
this.inventory = new InventoryService();
this.payment = new PaymentService();
this.invoice = new InvoiceService();
this.email = new EmailService();
this.shipping = new ShippingService();
}
public OrderResult placeOrder(String customerId, String productId,
int quantity, double amount, String address) {
String orderId = "ORD-" + System.currentTimeMillis();
// Step 1: Check and reserve inventory
if (!inventory.checkStock(productId, quantity)) {
return new OrderResult(orderId, null, null, false, "Out of stock");
}
inventory.reserveStock(productId, quantity);
// Step 2: Process payment
String transactionId;
try {
transactionId = payment.charge(customerId, amount);
} catch (Exception e) {
return new OrderResult(orderId, null, null, false, "Payment failed");
}
// Step 3: Generate invoice
invoice.generateInvoice(orderId, customerId, amount);
// Step 4: Create shipment
String trackingId = shipping.createShipment(orderId, address);
// Step 5: Send confirmation
email.sendOrderConfirmation(customerId, orderId);
return new OrderResult(orderId, transactionId, trackingId, true, "Order placed successfully");
}
}
// Usage - Client code is clean and simple
public class Main {
public static void main(String[] args) {
OrderFacade orderFacade = new OrderFacade();
OrderResult result = orderFacade.placeOrder(
"CUST-42", "PROD-100", 2, 59.98, "123 Main St, Springfield"
);
System.out.println(result);
}
}
The same order processing facade scenario in Python.
from dataclasses import dataclass
import time
# Subsystem: Inventory
class InventoryService:
def check_stock(self, product_id: str, quantity: int) -> bool:
print(f"[Inventory] Checking stock for {product_id}")
return True
def reserve_stock(self, product_id: str, quantity: int) -> None:
print(f"[Inventory] Reserved {quantity} units of {product_id}")
# Subsystem: Payment
class PaymentService:
def charge(self, customer_id: str, amount: float) -> str:
print(f"[Payment] Charging ${amount} to customer {customer_id}")
return f"TXN-{int(time.time() * 1000)}"
def refund(self, transaction_id: str) -> None:
print(f"[Payment] Refunding transaction {transaction_id}")
# Subsystem: Invoice
class InvoiceService:
def generate_invoice(self, order_id: str, customer_id: str, amount: float) -> str:
invoice_id = f"INV-{int(time.time() * 1000)}"
print(f"[Invoice] Generated {invoice_id} for order {order_id}")
return invoice_id
# Subsystem: Email
class EmailService:
def send_order_confirmation(self, customer_id: str, order_id: str) -> None:
print(f"[Email] Confirmation sent to customer {customer_id} for order {order_id}")
# Subsystem: Shipping
class ShippingService:
def create_shipment(self, order_id: str, address: str) -> str:
tracking_id = f"SHIP-{int(time.time() * 1000)}"
print(f"[Shipping] Created shipment {tracking_id} to {address}")
return tracking_id
@dataclass
class OrderResult:
order_id: str
transaction_id: str
tracking_id: str
success: bool
message: str
def __str__(self):
return (f"Order {self.order_id} | Txn: {self.transaction_id} | "
f"Tracking: {self.tracking_id} | {self.message}")
# FACADE: Simplifies the entire order process
class OrderFacade:
def __init__(self):
self._inventory = InventoryService()
self._payment = PaymentService()
self._invoice = InvoiceService()
self._email = EmailService()
self._shipping = ShippingService()
def place_order(self, customer_id: str, product_id: str,
quantity: int, amount: float, address: str) -> OrderResult:
order_id = f"ORD-{int(time.time() * 1000)}"
# Step 1: Check and reserve inventory
if not self._inventory.check_stock(product_id, quantity):
return OrderResult(order_id, None, None, False, "Out of stock")
self._inventory.reserve_stock(product_id, quantity)
# Step 2: Process payment
try:
transaction_id = self._payment.charge(customer_id, amount)
except Exception:
return OrderResult(order_id, None, None, False, "Payment failed")
# Step 3: Generate invoice
self._invoice.generate_invoice(order_id, customer_id, amount)
# Step 4: Create shipment
tracking_id = self._shipping.create_shipment(order_id, address)
# Step 5: Send confirmation
self._email.send_order_confirmation(customer_id, order_id)
return OrderResult(order_id, transaction_id, tracking_id, True, "Order placed successfully")
# Usage - Client code is clean and simple
if __name__ == "__main__":
order_facade = OrderFacade()
result = order_facade.place_order(
"CUST-42", "PROD-100", 2, 59.98, "123 Main St, Springfield"
)
print(result)
JdbcTemplate — Simplifies raw JDBC operations (connection management, statement creation, result set handling) behind clean methods$.ajax() was a facade over the verbose XMLHttpRequest APIrequests library — A facade over the complex urllib / http.client modules