Besoin d'un expert pour vous aider dans votre projet de développement Symfony ou PHP ? Demandez-nous un devis dès maintenant


Appliquer le Domain-Driven Design à PHP et Symfony : Un Guide Pratique

· Silas Joisten · Temps de lecture: 4 minutes
Domain Driven Design practical approach

Le Domain-Driven Design (DDD) s'applique à Symfony grâce à des Value Objects, des dépôts et des contextes bornés. Dans cet article, découvrez les étapes concrètes pour construire des applications PHP évolutives.

Introduction

Le Domain-Driven Design (DDD) aide les développeurs à structurer leurs applications autour de la logique métier concrète tout en gardant une séparation claire des responsabilités. Dans mon précédent article, j’ai expliqué les fondements théoriques du DDD. Aujourd’hui, nous allons plonger dans un exemple concret en construisant un client API météo utilisant OpenWeatherMap et Symfony.

Qu'allez-vous apprendre ?

  • Comment structurer un client API selon les principes du Domain-Driven Design

  • Utiliser des Value Objects pour encapsuler la logique des requêtes et réponses

  • Implémenter un repository pour gérer les interactions avec l'API

  • Séparer la logique API de la logique métier

  • Utiliser des clients HTTP scénarisés dans Symfony pour une meilleure configuration

À la fin de cet article, vous serez en mesure d'intégrer des APIs tierces dans Symfony tout en maintenant une architecture propre.

Cas d’utilisation : récupération de données météo via OpenWeatherMap

Nous allons construire un client API conforme au DDD pour récupérer les données météo en temps réel depuis OpenWeatherMap.

Détails de l’API

URL de base :

https://api.openweathermap.org/data/2.5/weather

Paramètres de requête :

  • q (Nom de la ville, par ex. "Paris")

  • units (Unités : "metric", "imperial", "standard")

Exemple de requête :

GET https://api.openweathermap.org/data/2.5/weather?q=Paris&units=metric&appid=YOUR_API_KEY

Exemple de réponse :

{
  "coord": { "lon": 2.3488, "lat": 48.8534 },
  "weather": [{ "main": "Clouds", "description": "few clouds" }],
  "main": { "temp": 18.3, "pressure": 1015, "humidity": 70 },
  "wind": { "speed": 3.5, "deg": 150 },
  "name": "Paris"
}

Étape 1 : Définir l’interface de l’API

Pour découpler la logique de l'API du reste de l'application, nous définissons une interface qui décrit comment doit fonctionner le client API.

// ...
namespace App\Weather\Api;

use App\Weather\Domain\WeatherRequest;
use App\Weather\Domain\WeatherResponse;
use App\Weather\Domain\Region;

interface WeatherApiInterface
{
    public function forRegion(Region $region, WeatherRequest $request = new WeatherRequest()): WeatherResponse;
}

Pourquoi utiliser une interface ?

  • Elle abstrait les dépendances externes (par ex. OpenWeatherMap)

  • Elle assure la flexibilité (facile à changer de fournisseur API)

  • Elle permet l’injection de dépendance pour les tests ou les mocks

Notre application reste ainsi faiblement couplée, extensible et testable.

Étape 2 : Implémenter les Value Objects

Region (Value Object)

Elle encapsule et valide les noms de ville.

// ...
use Webmozart\Assert\Assert;

final readonly class Region
{
    public function __construct(public string $value)
    {
        Assert::stringNotEmpty($value);
        Assert::notWhitespaceOnly($value);
    }
}

Unit (Enum)

Elle représente les unités disponibles avec une sécurité de type stricte.

enum Unit: string
{
    case METRIC = 'metric';
    case IMPERIAL = 'imperial';
    case STANDARD = 'standard';
}

WeatherRequest (Value Object)

Elle représente les options de requête de l’API.

// ...
final readonly class WeatherRequest implements \JsonSerializable
{
    public function __construct(
        public Unit $unit = Unit::METRIC,
    ) {
    }

    public function jsonSerialize(): array
    {
        return [
            'unit' => $this->unit->value,
        ];
    }
}

WeatherResponse (Value Object)

Elle valide et structure les données retournées par l’API.

use Webmozart\Assert\Assert;

final readonly class WeatherResponse
{
    public function __construct(array $values)
    {
        Assert::keyExists($values, 'name');
        $this->city = $values['name'];

        Assert::keyExists($values, 'main');
        Assert::keyExists($values['main'], 'temp');
        $this->temperature = $values['main']['temp'];

        Assert::keyExists($values, 'weather');
        Assert::isList($values['weather']);
        Assert::count($values['weather'], 1);
        Assert::keyExists($values['weather'][0], 'description');
        $this->description = $values['weather'][0]['description'];
        
        // of course, this is a very simplified example
    }
}

Pourquoi utiliser des Value Objects ?

  • Assurer l’intégrité des données

  • Encapsuler la logique métier

  • Prévenir les données invalides

  • Faciliter les tests et la lecture du code

Les enums comme Unit améliorent aussi l’expérience développeur en imposant des valeurs valides.

Étape 3 : Implémenter le client API météo

Voici une implémentation du client avec Symfony HttpClient :

// ...
final class WeatherApi implements WeatherApiInterface
{
    public function __construct(
        private HttpClientInterface $client,
        #[\SensitiveParameter]
        #[Autowire(env: 'OPENWEATHER_API_KEY')]
        private string $apiKey
    ) {}

    public function forRegion(Region $region, WeatherRequest $request = new WeatherRequest()): WeatherResponse
    {
        $response = $this->client->request('GET', 'https://api.openweathermap.org/data/2.5/weather', [
            'query' => [...$request->jsonSerialize(), 'q' => $region->name, 'appid' => $this->apiKey],
        ]);

        return new WeatherResponse($response->toArray());
    }
}

Une alternative : le client HTTP scénarisé

framework:
  http_client:
    scoped_clients:
      openweather.client:
        base_uri: 'https://api.openweathermap.org/data/2.5/weather'
        headers:
          Accept: 'application/json'
        query:
          appid: '%env(OPENWEATHER_API_KEY)%'

Modifiez le client comme suit :

final class WeatherApi implements WeatherApiInterface
{
    public function __construct(
+        private HttpClientInterface $openweatherClient,
-        private HttpClientInterface $client,
-        #[\SensitiveParameter]
-        #[Autowire(env: 'OPENWEATHER_API_KEY')]
-        private string $apiKey
    ) {}

    public function forRegion(Region $region, WeatherRequest $request = new WeatherRequest()): WeatherResponse
    {
-        $response = $this->client->request('GET', 'https://api.openweathermap.org/data/2.5/weather', [
-            'query' => [...$request->jsonSerialize(), 'q' => $region->name, 'appid' => $this->apiKey],
+        $response = $this->openweatherClient->request('GET', 'https://api.openweathermap.org/data/2.5/weather', [
+            'query' => [...$request->jsonSerialize(), 'q' => $region->name],

        ]);

        return new WeatherResponse($response->toArray());
    }
}

Étape 4 : Organisation du répertoire

En Domain-Driven Design, une structure claire est essentielle. Nous utilisons le Bridge Pattern pour isoler la logique externe de l’API.

src/
├── Bridge/
│   ├── WeatherApi/
│   │   ├── Domain/
│   │   │   ├── Region.php
│   │   │   ├── Unit.php
│   │   │   ├── WeatherRequest.php
│   │   │   ├── WeatherResponse.php
│   │   ├── WeatherApi.php
│   │   ├── WeatherApiInterface.php

Pourquoi cette structure ?

  • Isoler l’intégration API sous Bridge\WeatherApi

  • Garder la logique métier indépendante de l’API

  • Faciliter la maintenance et les tests

Il est possible de changer de fournisseur d’API sans impacter la logique métier.

Conclusion

Cet exemple montre comment intégrer une API externe dans Symfony avec le Domain-Driven Design. En résumé, il faut :

  • Encapsuler la logique API dans un repository

  • Utiliser des Value Objects pour les requêtes/réponses

  • Masquer l’intégration API derrière une interface

  • (Optionnel) Utiliser des clients HTTP scénarisés Symfony

Avec cette architecture, votre client API Symfony sera maintenable, testable et découplé.

Prêts à appliquer le Domain-Driven Design à vos projets Symfony ?

Les experts Symfony et PHP de SensioLabs vous aident à créer un projet efficace en matière de structure et avec une analyse basée sur le domaine.

cookie

Ce site utilise des cookies et vous donne le contrôle sur ceux que vous souhaitez activer. Lire la politique de confidentialité

Image