0

Facade — simplifying a complex subsystem (the pattern, vs Laravel Facade)

Intermediate5 min read·eng-03-003
interviewcompare

Concept

Facade pattern (GoF) provides a simple, unified interface to a complex subsystem. It doesn't add functionality — it simplifies access by hiding complexity behind a clean surface.

The confusion with Laravel Facades: Laravel uses the term "Facade" for something different — a static proxy to a service container binding. Cache::get('key') appears to be a static call but actually resolves a service from the container and calls get() on it. This is better described as a static proxy or service locator, not the GoF Facade pattern (though it serves a similar simplification purpose).

GoF Facade pattern: A class that wraps a subsystem. Users interact only with the Facade. The Facade coordinates the subsystem's components. The subsystem components aren't hidden — you CAN access them directly — but most users don't need to.

Example: An OrderFacade wraps: inventory check, payment processing, order persistence, notification dispatch, and invoice generation. The caller does $facade->placeOrder($cart) instead of orchestrating all 5 subsystems manually.

Facade vs Service class: In modern application architecture, a "Service" class plays the same role as a GoF Facade. The distinction is academic — both simplify access to complex subsystems.

Facade vs Adapter: Facade simplifies a complex interface; Adapter makes an incompatible interface compatible. Facade creates a NEW simple interface. Adapter wraps to match an EXISTING interface.

When NOT to use Facade: If the subsystem already has a simple interface, adding a Facade is just extra layers. Don't create Facades for Facades.

Code Example

php
<?php
// Subsystem classes — complex individually
class InventorySystem
{
    public function checkStock(int $productId, int $qty): bool { return true; }
    public function reserveStock(int $productId, int $qty): void { }
    public function releaseReservation(int $productId, int $qty): void { }
}

class PaymentSystem
{
    public function charge(string $paymentMethodId, int $amountCents): array
    {
        return ['id' => 'pay_123', 'status' => 'succeeded'];
    }
    public function refund(string $paymentId): void { }
}

class OrderRepository
{
    public function create(array $data): int { return 1; }
    public function update(int $id, array $data): void { }
}

class NotificationSystem
{
    public function sendOrderConfirmation(int $userId, int $orderId): void { }
}

// Facade — simplifies the complex ordering process
class OrderFacade
{
    public function __construct(
        private readonly InventorySystem   $inventory,
        private readonly PaymentSystem     $payment,
        private readonly OrderRepository   $orders,
        private readonly NotificationSystem $notifications,
    ) {}

    public function placeOrder(int $userId, string $paymentMethodId, array $items): int
    {
        // Coordinate the subsystems
        foreach ($items as $item) {
            if (!$this->inventory->checkStock($item['product_id'], $item['quantity'])) {
                throw new \DomainException("Product {$item['product_id']} is out of stock.");
            }
        }

        foreach ($items as $item) {
            $this->inventory->reserveStock($item['product_id'], $item['quantity']);
        }

        $total  = $this->calculateTotal($items);
        $charge = $this->payment->charge($paymentMethodId, $total);

        $orderId = $this->orders->create([
            'user_id'    => $userId,
            'payment_id' => $charge['id'],
            'total'      => $total,
            'items'      => $items,
        ]);

        $this->notifications->sendOrderConfirmation($userId, $orderId);

        return $orderId;
    }

    private function calculateTotal(array $items): int
    {
        return array_sum(array_column($items, 'price')) * 100; // cents
    }
}

// Controller — simple call through Facade
class OrderController
{
    public function __construct(private readonly OrderFacade $orderFacade) {}

    public function store(Request $request): JsonResponse
    {
        $orderId = $this->orderFacade->placeOrder(
            auth()->id(),
            $request->input('payment_method'),
            $request->input('items')
        );
        return response()->json(['order_id' => $orderId], 201);
    }
}

// Laravel's "Facade" is a DIFFERENT concept — a static proxy:
// Cache::get('key') → app('cache')->get('key') → resolves from service container