Facade Pattern

Introduction

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.

The Problem

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.

The Solution

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.

Key Principle

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.

Java Example

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

Python Example

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)

When to Use

  • Complex subsystem orchestration — When a single operation requires coordinating multiple services in a specific order
  • API simplification — When you want to provide a simple interface to a complex library or framework
  • Layer isolation — When you want to decouple your application layers (e.g., controller layer from service layer internals)
  • Legacy wrapping — When you need to present a modern, clean API over messy legacy code

Real-World Usage

  • Spring Boot’s JdbcTemplate — Simplifies raw JDBC operations (connection management, statement creation, result set handling) behind clean methods
  • SLF4J — Provides a simple logging facade over complex logging frameworks
  • jQuery$.ajax() was a facade over the verbose XMLHttpRequest API
  • Python’s requests library — A facade over the complex urllib / http.client modules
  • AWS SDK — High-level resource interfaces that simplify low-level API calls



Subscribe To Our Newsletter
You will receive our latest post and tutorial.
Thank you for subscribing!

required
required


Leave a Reply

Your email address will not be published. Required fields are marked *