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.
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:
- Subsystem Services: Separate services for users, orders, and inventory.
- Remote Facade: A class that acts as a single entry point for the client.
- 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
Setting Up Autoloading
To make namespaces work, configure Composer.
{
"autoload": {
"psr-4": {
"Services\\": "src/Services/",
"Facade\\": "src/Facade/"
}
}
}
composer dump-autoload
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
];
}
}
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
]
];
}
}
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
];
}
}
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
];
}
}
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);
📌 To test the API, start a local PHP server and visit:
http://localhost:8000/api/dashboard.php?userId=1&productId=2001
{
"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
}
}
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>";
Start a local server:
php -S localhost:8000 -t public
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
)
)
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)
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.
I've changed the example to one with more explanation.