DEV Community

Cover image for PHP Design Patterns: Remote Facade
Antonio Silva
Antonio Silva

Posted on • Edited on

1

PHP Design Patterns: Remote Facade

Remote Facade Design Pattern

The Remote Facade design pattern is used to reduce the number of remote calls between the client and the server. It acts as a single entry point to the underlying subsystem, offering coarse-grained methods that aggregate and manage the details of more complex operations. This pattern helps minimize latency issues caused by multiple network requests by wrapping complex processes inside a unified interface.

In PHP, the Remote Facade pattern is particularly useful in cases where the server provides a RESTful API or web services to a client.

facade

How It Works

  • The Facade class simplifies the interaction with various subsystems or services.
  • The client makes a single call to the Facade instead of multiple fine-grained calls.
  • The Facade coordinates with underlying services, performs any necessary data processing, and returns a simpler response to the client.

Implementation

Let's create a detailed example of the Remote Facade pattern using PHP, simulating a real-world e-commerce system with a REST API. This example will cover:

  1. Subsystem Services: Separate services for users, orders, and inventory.
  2. Remote Facade: A class that acts as a single entry point for the client.
  3. Client Request: Simulating how a client (like a frontend or mobile app) interacts with the API.

Folder Structure

ecommerce-app/
│── public/                  # Public-facing directory
│   ├── index.php            # Main entry point
│
│── src/                     # Core application logic
│   ├── Services/            # Subsystems (fine-grained remote services)
│   │   ├── UserService.php
│   │   ├── OrderService.php
│   │   ├── InventoryService.php
│   │
│   ├── Facade/              # Remote Facade
│   │   ├── ECommerceFacade.php
│   │
│   ├── Config/              # Configuration files (optional)
│   │   ├── config.php
│
│── api/                     # API layer (if exposing via REST)
│   ├── dashboard.php        # API endpoint using the facade
│
│── tests/                   # Unit tests for services and facade
│
│── vendor/                  # Composer dependencies (if used)
│
│── composer.json            # PHP dependency manager file
Enter fullscreen mode Exit fullscreen mode

Setting Up Autoloading

To make namespaces work, configure Composer.

{
    "autoload": {
        "psr-4": {
            "Services\\": "src/Services/",
            "Facade\\": "src/Facade/"
        }
    }
}
Enter fullscreen mode Exit fullscreen mode
composer dump-autoload
Enter fullscreen mode Exit fullscreen mode

1️⃣ Implementing the Folder Structure

1.1 - src/Services/ (Subsystem Services)

Each service simulates a remote API or database call.

src/Services/UserService.php

<?php
namespace Services;

class UserService {
    /**
     * Get user information by user ID.
     *
     * @param int $userId
     * @return array
     */
    public function getUserInfo(int $userId): array {
        // Simulate data fetched from a database or external API
        return [
            'id' => $userId,
            'name' => 'Alice Johnson',
            'email' => 'alice@example.com',
            'loyaltyPoints' => 120
        ];
    }
}
Enter fullscreen mode Exit fullscreen mode

src/Services/OrderService.php

<?php
namespace Services;

class OrderService {
    /**
     * Get the user's most recent orders.
     *
     * @param int $userId
     * @return array
     */
    public function getRecentOrders(int $userId): array {
        // Simulate order data (e.g., from a database or API)
        return [
            [
                'orderId' => 101,
                'status' => 'Delivered',
                'totalAmount' => 250.50
            ],
            [
                'orderId' => 102,
                'status' => 'Pending',
                'totalAmount' => 89.99
            ]
        ];
    }
}
Enter fullscreen mode Exit fullscreen mode

src/Services/InventoryService.php

<?php
namespace Services;

class InventoryService {
    /**
     * Check the stock level of a given product.
     *
     * @param int $productId
     * @return array
     */
    public function checkStock(int $productId): array {
        // Simulate stock information
        return [
            'productId' => $productId,
            'stockLevel' => 15,
            'reorderThreshold' => 5
        ];
    }
}
Enter fullscreen mode Exit fullscreen mode

1.2 - src/Facade/(Remote Facade)

This ECommerceFacade.php aggregates responses from multiple services.

src/Facade/ECommerceFacade.php

<?php
namespace Facade;

use Services\UserService;
use Services\OrderService;
use Services\InventoryService;

/**
 * ECommerceFacade simplifies interactions with multiple services.
 * This is the Remote Facade entry point for client applications.
 */
class ECommerceFacade {
    private UserService $userService;
    private OrderService $orderService;
    private InventoryService $inventoryService;

    /**
     * Constructor initializes the service objects.
     */
    public function __construct() {
        $this->userService = new UserService();
        $this->orderService = new OrderService();
        $this->inventoryService = new InventoryService();
    }

    /**
     * Get dashboard data for a user, including user info,
     * recent orders, and stock info for a selected product.
     *
     * @param int $userId
     * @param int $productId
     * @return array
     */
    public function getDashboardData(int $userId, int $productId): array {
        // Retrieve user information
        $userInfo = $this->userService->getUserInfo($userId);

        // Retrieve user's recent orders
        $recentOrders = $this->orderService->getRecentOrders($userId);

        // Retrieve stock information for a specific product
        $productStock = $this->inventoryService->checkStock($productId);

        // Aggregate and return the data to the client
        return [
            'user' => $userInfo,
            'recentOrders' => $recentOrders,
            'productStock' => $productStock
        ];
    }
}
Enter fullscreen mode Exit fullscreen mode

2️⃣ Exposing as an API

If you want to use this in an API, create an api/dashboard.php file.

api/dashboard.php

<?php
require_once __DIR__ . '/../vendor/autoload.php';

use Facade\ECommerceFacade;

// Set response type to JSON
header('Content-Type: application/json');

// Retrieve query parameters (with defaults)
$userId = $_GET['userId'] ?? 1;
$productId = $_GET['productId'] ?? 2001;

// Create the facade instance
$facade = new ECommerceFacade();

// Get the dashboard data using the facade
$data = $facade->getDashboardData((int) $userId, (int) $productId);

// Output the data as JSON
echo json_encode($data, JSON_PRETTY_PRINT);
Enter fullscreen mode Exit fullscreen mode

📌 To test the API, start a local PHP server and visit:

http://localhost:8000/api/dashboard.php?userId=1&productId=2001
Enter fullscreen mode Exit fullscreen mode
{
  "user": {
    "id": 1,
    "name": "Alice Johnson",
    "email": "alice@example.com",
    "loyaltyPoints": 120
  },
  "recentOrders": [
    {
      "orderId": 101,
      "status": "Delivered",
      "totalAmount": 250.5
    },
    {
      "orderId": 102,
      "status": "Pending",
      "totalAmount": 89.99
    }
  ],
  "productStock": {
    "productId": 2001,
    "stockLevel": 15,
    "reorderThreshold": 5
  }
}
Enter fullscreen mode Exit fullscreen mode

3️⃣ Running the Application

If you don’t want an API and just want to test the facade, use public/index.php:

public/index.php

<?php
require_once __DIR__ . '/../vendor/autoload.php';

use Facade\ECommerceFacade;

// Create the facade instance
$facade = new ECommerceFacade();

// Fetch dashboard data
$data = $facade->getDashboardData(1, 2001);

// Print results to browser (for testing purposes)
echo "<pre>";
print_r($data);
echo "</pre>";
Enter fullscreen mode Exit fullscreen mode

Start a local server:

php -S localhost:8000 -t public
Enter fullscreen mode Exit fullscreen mode
Array
(
    [user] => Array
        (
            [id] => 1
            [name] => Alice Johnson
            [email] => alice@example.com
            [loyaltyPoints] => 120
        )

    [recentOrders] => Array
        (
            [0] => Array
                (
                    [orderId] => 101
                    [status] => Delivered
                    [totalAmount] => 250.5
                )

            [1] => Array
                (
                    [orderId] => 102
                    [status] => Pending
                    [totalAmount] => 89.99
                )

        )

    [productStock] => Array
        (
            [productId] => 2001
            [stockLevel] => 15
            [reorderThreshold] => 5
        )

)
Enter fullscreen mode Exit fullscreen mode

Why Use Remote Facade?

Reduces multiple API calls → Instead of calling three services separately, the client makes a single request.
Encapsulates complex logic → The client does not need to worry about how the data is fetched or aggregated.
Improves performance → Reduces network latency by combining responses.
Enhances maintainability → Changes to services do not affect the client, as long as the facade interface remains stable.

When Should You Use This Pattern?

🔹 When you have a distributed system with multiple microservices.
🔹 When your client should not directly interact with complex subsystems.
🔹 When you want to minimize network requests and improve performance.

Top comments (2)

Collapse
 
xwero profile image
david duymelinck

I think the example doesn't make it clear what the benefit is.
For me each service connects to a different server. So there is no way to reduce requests.

Lets take the order as the example. The order service can have order, orderUser and orderProducts endpoints and they all use the order id to identify the data.
But the application needs all three endpoints as a single object. That is the moment a facade can be created with the getOrderDetails method.
It doesn't matter how the order service implements it, but it should be possible to get information in a single request, instead of three requests.

It is possible to add the getOrderDetails method to the service class. I think it depends on how much preparation is needed to get the aggregated data. And if that preparation is useful for other endpoints.

I would move the method from the service to a facade when for example the user details can come from the application session because the user is logged in. I think it is fair to assume this will not be the only case where the user details are a part of another object.

Collapse
 
xxzeroxx profile image
Antonio Silva • Edited

I've changed the example to one with more explanation.