Accueil / Tutoriels / Proxy

Proxy

Publié le 2 Juin 2025
Conception

Proxy est un patron de conception structurel qui fournit un substitut d’un autre objet pour contrôler l’accès à celui-ci. Il est très utile pour ajouter un comportement à un objet sans modifier son code.

Principe

Le Proxy agit comme un intermédiaire qui implémente la même interface que l’objet qu’il représente et redirige les appels vers ce dernier en y ajoutant si besoin une logique autour de cet appel.

Le pattern Proxy est utilisé quand l’objet réel est coûteux à créer ou à utiliser, quand il faut contrôler l’accès à un objet (droits, authentification), quand il faut enrichir des appels (logs, cache, etc…) ou centraliser l’accès à un objet distant.

Exemples d’utilisations

Proxy d'authentification et contrôle d’accès d’une API

Dans une API REST, on veut protéger certaines routes (/admin) pour qu’elles ne soient accessibles qu’aux utilisateurs authentifiés ou à certains rôles (admin, manager, etc …).

Pour notre exemple, un objet Proxy va se placer entre le client et le service réel (AdminController) et intercepter les appels pour vérifier la présence d’un token JWT, valider les permissions ou autoriser/bloquer l’accès aux services. De cette manière, on centralise toute la logique d’authentification/autorisation, notre code métier n’est pas “pollué” par des vérifications de droits, d’accès ou de blocage.

Proxy pour la mise en cache d’appels à une API externe

Une application web appelle une API tierce avec un temps de réponse lent ou des réponses facturées à la requête.

Un objet proxy va intercepter les appels à cette API pour vérifier si une réponse identique existe déjà en cache. Si oui, il retourne la donnée en cache et dans le cas contraire il fera un appels vers l’API pour récupérer la donnée et la mettre en cache avant de la retourner. On réduit ainsi le temps de réponse, les coûts et on améliore la résilience de notre application (si l’API est temporairement indisponible, on peut se servir du cache).

Points d’attentions

Le code ne communique plus directement avec l’objet réel, ce qui ajoute de la complexité supplémentaire. Le fil d’exécution peut être moins clair et des bugs liés à une mauvaise implémentation du proxy.

Si on crée plusieurs proxys pour différents cas (authentification, cache, logs, etc…), on peut réécrire plusieurs fois la même logique autour d’objets différents. La maintenance et le refactoring peuvent devenir plus complexes.

Le proxy expose la même interface que l’objet réel, mais il en change le comportement. Un développeur peut croire qu’il interagit avec l’objet original alors que ce n’est pas le cas.

Implémenter un Proxy

Tout d’abord on va définir une interface que l’objet réel et le proxy vont partager. Cette interface contient les méthodes publiques exposées. Le client ne doit pas savoir s’il parle au vrai service ou au proxy.

Au niveau de l’objet réel, avec notre interface, on implémente toute la logique métier nécessaire. Le but du proxy n’est pas de réécrire toute cette logique mais d’y accéder de manière contrôlée.

Nous allons également implémenté la même interface que l’objet réel mais en y ajoutant une couche de logique autour des appels.

Le proxy peut instancier lui-même l’objet réel ou le recevoir par injection de dépendance. Le client appelle ensuite une instance qui implémente l’interface sans avoir à se soucier s’il parle au service réel ou non.

Voici un exemple en Go :

On va imaginer un service WeatherService qui interroge une API météo. Cette API étant coûteuse, nous allons utiliser un proxy pour la mise en cache.


package weather

import (
   "fmt"
   "time"
)

// WeatherService Interface
type WeatherService interface {
   GetWeather(city string) string
}

// RealWeatherService Real service (simule un appel long)
type RealWeatherService struct{}

func (r *RealWeatherService) GetWeather(city string) string {
   time.Sleep(2 * time.Second) // simulation d’un appel lent
   return fmt.Sprintf("Météo en %s : Ensoleillé", city)
}


package weather

import "fmt"

type WeatherServiceProxy struct {
   realService WeatherService
   cache       map[string]string
}

func NewWeatherServiceProxy(real WeatherService) *WeatherServiceProxy {
   return &WeatherServiceProxy{
      realService: real,
      cache:       make(map[string]string),
   }
}

func (p *WeatherServiceProxy) GetWeather(city string) string {
   if val, found := p.cache[city]; found {
      return fmt.Sprintf("[CACHE] %s", val)
   }

   val := p.realService.GetWeather(city)
   p.cache[city] = val
   return val
}


package main

import (
   "fmt"
   apiweather "designpatterns/proxy/weather"
)

func main() {
   realService := &apiweather.RealWeatherService{}
   proxy := apiweather.NewWeatherServiceProxy(realService)


   fmt.Println(proxy.GetWeather("Paris"))
   fmt.Println(proxy.GetWeather("Paris")) // Doit être en cache
}


Le même exemple en PHP :


namespace Practice\DesignPatterns\Proxy;

class RealWeatherService implements WeatherServiceInterface
{
   public function getWeather(string $city): string
   {
       sleep(2); // Simule un appel lent ou coûteux
       return "Météo à $city : Ensoleillé";
   }
}


namespace Practice\DesignPatterns\Proxy;

interface WeatherServiceInterface
{
   public function getWeather(string $city): string;
}


namespace Practice\DesignPatterns\Proxy;

class WeatherServiceProxy implements WeatherServiceInterface
{
   private WeatherServiceInterface $realService;
   private array $cache = [];

   public function __construct(WeatherServiceInterface $realService)
   {
       $this->realService = $realService;
   }

   public function getWeather(string $city): string
   {
       if (isset($this->cache[$city])) {
           return "[CACHE] " . $this->cache[$city];
       }

       $result = $this->realService->getWeather($city);
       $this->cache[$city] = $result;

       return $result;
   }
}


declare(strict_types=1);

use Practice\DesignPatterns\Proxy\RealWeatherService;
use Practice\DesignPatterns\Proxy\WeatherServiceProxy;

require "./vendor/autoload.php";

$realService = new RealWeatherService();
$proxy = new WeatherServiceProxy($realService);

// Premier appel — lent
echo $proxy->getWeather('Paris') . PHP_EOL;

// Deuxième appel — depuis le cache
echo $proxy->getWeather('Paris') . PHP_EOL;

// Autre ville — non encore en cache
echo $proxy->getWeather('Lyon') . PHP_EOL;

Conclusion

J'espère que grâce à cet article vous êtes désormais plus à l'aise avec le design pattern Bridge. Vous pouvez retrouver mon article sur les patrons de conception ou les autres tutoriels sur Bridge, Strategy, Decorator, Chain of responsibility et Factory.