Domain Driven Design avec PHP & Symfony
Tags: ddd, php, symfony, programming
Published: 2020-05-31 16:00:56

*english version [available here](https://dev.to/ludofleury/domain-driven-design-with-php-and-symfony-1bl6)*

Partage d'observations, considérations & recommandations concernant les développements pilotés par le métier avec [PHP](https://www.php.net/) et le framework [Symfony](https://symfony.com/) (version 2.x à 5.x)

*note: pas l'habitude de rédiger en français sur les sujets techniques. Les sources, liens & références seront souvent en anglais*

# Redonner du sens au développement
<abbr title="Domain Driven Design">DDD</abbr> gagne en popularité dans la communauté Symfony/PHP. J'évoquais précédemment [ma quête insatiable de sens](https://dev.to/ludofleury/big-ball-of-mud-1130) qui motive mon évangélisation toute particulière pour le DDD.

## Définition
<abbr title="Domain Driven Design">DDD</abbr> c'est un [paradigme](https://fr.wikipedia.org/wiki/Paradigme#:~:text=Un%20paradigme%20est%20%E2%80%94%20en%20%C3%A9pist%C3%A9mologie,th%C3%A9orique%2C%20courant%20de%20pens%C3%A9e), une approche, une méthode, une façon de penser et de voir. 

**La valeur du DDD réside dans sa définition: "développement piloté par le métier"**. Si vous ne devez retenir et comprendre qu'une seule chose du DDD: c'est bien son intitulé. Si vous restez concentré sur le métier, le contexte métier, les règles métiers, le vocabulaire métiers, les acteurs métiers dès que vous lancez votre IDE pour coder: votre code sera en accord avec les principes DDD.

Le **DDD n'est pas une architecture logicielle** ou un style architectural. Les "Entities", les "Value Objects" ou les "Aggregates Root" ne sont que des détails techniques trompeurs, uniquement utiles dans nos univers orientés objets. 

**Le DDD c'est essentiellement une compréhension du métier cristallisée dans un dépôt de code par les développeurs**.


## Concrètement
Si les chefs de projets, les "stakeholders" ou les responsables métiers prenaient le temps de lire le contenu d'un dépôt DDD, ils devraient normalement comprendre 80% du code. 

En réalité les [langages de programmation de haut niveau](https://en.wikipedia.org/wiki/High-level_programming_language) comme PHP peuvent être considérés comme une version extrêmement  appauvrie de l'anglais. En DDD, le code PHP décrit une règle métier ou un concept métier en "anglais orienté-objet".

A quoi ressemble mon code ? A des instructions métiers écrites dans un très mauvais anglais :

```php
$cashier->checkout($cart) // $caissier->encaisse($panier)
$cashier->charge($card) // $caissier->debite($carte)
$accountant->write($payment) // $comptable->ecrit($paiement)
$compliance->verify($payment) // $controlleurFinancier->verifie($paiement)
```

A quoi ressemble mes classes "domain" (métiers) ? Là encore, à des concepts métiers écrits dans un anglais particulier:

```php
namespace Legal\Company\France;

/**
 * French unique registration number for a specific establishments of a company
 * issued by INSEE
 * SIRET: "Système d'Identification du Répertoire des ETablissements"
 * 
 * @see https://www.economie.gouv.fr/entreprises/numeros-siren-siret
 */
Class Siret implements RegistrationNumber
{
    private Siren $siren;
    private Nic $nic;

    public function __construct(string $number)
    {
        try {
            $this->siren = new Siren(mb_substr($number, 0, 9));
            $this->nic = new Nic(mb_substr($number, 9));
        } catch (\Exception $exception) {
            throw new \DomainException('Invalid SIRET number');
        }
    }

    public function getNumber(): string
    {
        return $this->siren->getNumber() . $this->nic->getNumber()
    }
}
```

```php
namespace Legal\Company\France;

/**
 * Unique registration number for a french company
 * Issued by INSEE, composing the first part of SIRET
 * Système d'Identification du Répertoire des ENtreprises
 *
 * @see https://bpifrance-creation.fr/encyclopedie/formalites-creation-dune-entreprise/formalites-generalites/numeros-didentification
 */
class Siren
{
    private string $number;
    
    public function __construct(string $number)
    {
         if (preg_match('/^[0-9]{9}$/', $number) !== 1) {
            throw new \DomainException('Invalid SIREN number');
         }

         $this->number = $number;
    }

    public function getNumber(): string
    {
        return $this->number;
    }
}
```

Les devs français sont sûrement familiers avec les concepts métiers ci-dessus. En revanche, si vous ne connaissez pas le fonctionnement des immatriculations de société en France, en lisant simplement ce code vous devriez avoir une idée assez claire sur le sujet.

## Les vertus
**Le code est "[expressif](https://medium.com/@daniel.oliver.king/writing-expressive-code-b69ef7a5a2fa#:~:text=This%20is%2C%20I%20think%2C%20one,human%20who%20reads%20our%20code.)"** pas seulement "lisible". Il traduit mot pour mot les concepts et le contexte métier.

Le code n'est pas seulement "expressif", il est également "[immutable](https://en.wikipedia.org/wiki/Immutable_object)". Je ne me suis pas imposé un design pattern ou une "best practice". J'ai simplement respecté les règles des immatriculations d'entreprises françaises (SIRET, SIREN & NIC), ces numéros sont immuables donc mon code est immuable... et donc ces objets s'apparentent naturellement à des "Value Objects", chouette. Le fait qu'ils soient des <abbr title="Value Object">VO</abbr> n'est donc qu'une simple conséquence de l'implémentation des spécifications métiers!

## [La cuillère n'existe pas](https://youtu.be/HW1XZcs0UU8)

Oubliez les "best practices", les "design patterns", les "fluent interface", "event dispatching", <abbr title="">MVC</abbr>, <abbr title="Separation of Concerns">SoC</abbr>, <abbr title="Single Responsibility Principle">SRP</abbr>, <abbr title="Service Oriented Architecture">SOA</abbr>, <abbr title="Rapid Application Deployment">RAD</abbr>, <abbr title="Create Read Update Delete">CRUD</abbr>, <abbr title="Don't Repeat Yourself">DRY</abbr>... **Oubliez la tech, seul le métier pilote l'implémentation, le métier est l'unique priorité.**

Utilisez votre langage (ici PHP) pour modéliser le métier le plus simplement possible. Tel l'expert qui explique son métier à un junior, le dev "explique" explicitement et clairement dans son code un process métier.

Si vous implémentez des Entités avec des setters & getters, des "Value Object" dans le namespace `\ValueObject\`, vous êtes en train de manquer l'essentiel du DDD.

J'insiste: le DDD ne s'intéresse pas à l'implémentation, DDD c'est le partage d'un langage commun entre le métier et le code.

Dernière remarque au sujet de la valeur du DDD:
Toutes les bases de code ont de la valeur au moment de l'exécution par la machine. **Concernant les bases de code DDD, elles ont une très forte valeur même non-exécutées. Ce sont des centres de connaissance, des patrimoines métiers pour les personnes qui travaillent avec.**

# DDD et Symfony
J'ai découvert le DDD autour de 2014, il m'a fallu une année complète, intensive de lecture et d'implémentation pour me sentir à l'aise et "efficient" avec mes outils (PHP, Symfony & Doctrine). Car comme [Evans](https://twitter.com/ericevans0) le mentionne:

> "Don't fight your framework".
>
> -- [Domain-driven Design: Tackling Complexity in the Heart of Software](https://www.amazon.com/Domain-Driven-Design-Tackling-Complexity-Software/dp/0321125215)

En fonction des projets et de leur complexité, j'adapte mes implémentations. Mais généralement lorsque j'amorce un nouveau projet ou que je coach un nouveau dev sur le DDD, je suis ces grandes lignes directrices:

* J'utilise & j'abuse du [command pattern](https://olvlvl.com/2018-04-command-dispatcher-pattern) ([Symfony Messenger](https://symfony.com/doc/current/components/messenger.html) est parfait)

* Je n'utilise pas le pattern [<abbr title="Command and Query Responsibility Segregation">CQRS</abbr>](https://martinfowler.com/bliki/CQRS.html)

* Je n'utilise pas le pattern [<abbr title="Event Sourcing">ES</abbr>](https://martinfowler.com/eaaDev/EventSourcing.html)

* J'utilise [Behat](https://github.com/Behat/Behat) & le [<abbr title="Behavior Driven Development">BDD</abbr>](https://www.arolla.fr/blog/2012/06/bdd-c-est-quoi-donc/) au niveau de mes commandes/handlers, couverture de test intégrale

* Je n'utilise que très rarement les tests unitaires, 20-30% de couverture en général.

## Pourquoi ni CQRS ni d'ES ?

Parce que ça coûte un rein! (voir [l'article suivant qui présente la stack soft & hard d'un ES](https://medium.com/tiller-systems/pourquoi-avoir-choisi-dutiliser-l-architecture-cqrs-e04c082f8b5f).)
Ce sont des solutions ultra puissantes. Mais à déployer uniquement lorsque cela fait sens (rapport coût & besoin).

### ES
Je pense qu'il n'y a pas beaucoup de dev php qui sont familiers avec l'event sourcing, encore moins qui sont expérimentés pour créer, maintenir ou modéliser un event store correctement dans le temps. Souvent, au début d'un projet on manque de "maturité" modèle, on apprend, on refacto et on migre.  Mais sur un event store, aucune migration. L'historique reflète directement les erreurs d'implémentation passées, ce qui multiplie drastiquement les coût de maintenance logicielle.

### CQRS
Le CQRS c'est vraiment puissant quand on passe sur des problématiques/volumes de très grande échelle. Dans ce type de contexte, le CQRS apporte directement la complexité de la "[Cohérence à terme](https://fr.wikipedia.org/wiki/Coh%C3%A9rence_(donn%C3%A9es)#Coh%C3%A9rence_%C3%A0_terme)" aka [eventual consistency](https://stackoverflow.com/questions/10078540/eventual-consistency-in-plain-english). Je vous laisse envisager le (sur)coût de cette spécificité.

*j'ai eu des expériences avec les 2 patterns, séparément et simultanément, dans des projets DDD, avec Symfony, parfois Doctrine et SQL/MongoDB*

## La structure 

* `src/Controller/`: Symfony controller
* `src/Command/`: Symfony CLI commands

* `src/Action/`: Command + CommandHandler 
* `src/Domain/`: AR, Entity, VO, Repository Interface

* `src/*`: Infrastructure, other stuff.

Loin d'être idéal, le répertoire nommé `/Domain/` est plutôt pratique. On peut remplacer le mot "Domain" par le nom spécifique du métier qu'on est en train d'adresser, par exemple: `/Ecommerce/`. 

### Le débat sur la pluralité des "domains"
Les questions existentielles apparaissent dès qu'on manipule plusieurs domaines dans son projet. Il y a des écoles & courants de pensées sur la question de l'existence du `Core Domain` et la séparation des `Supporting Domains`. De mon côté, c'est tranché,  ces derniers sont organisés sans distinction particulière dans `/Domain/`:

* `/Domain/Payment`
* `/Domain/Compliance`
* `/Domain/Delivery`

Problème réglé, c'est cohérent (consistent), ça gagne à tous les coups.

### Le contenu du "Domain"?

A l'intérieur de `src/Domain/`, j'ai l'ensemble des concepts métiers **utiles** à l'implémentation. Il y a certaines classes qui sont persistantes (en base de données), d'autres non. On y trouve également les services métiers.

Comme `src/Entity` n'existe plus, la configuration de Doctrine est modifiée pour scanner le répertoire `src/Domain/` dans son ensemble. Les annotations sont utilisées pour définir le mapping en base, car la [localité](Principe de localité), c'est également valable pour mon cerveau.

Evidemment, lorsque le projet s'alourdi, il ne faut pas hésiter à **ré-organiser `/Domain/` en reflétant les besoins métiers**:

```
/Domain/Legal/Company/France/RegistrationNumber/Siret.php
/Domain/Legal/Company/France/RegistrationNumber/Siren.php
/Domain/Legal/Company/France/RegistrationNumber/Nic.php
```

## [Commande qui conquiert](https://youtu.be/3HUWUtTZvK4?t=10)

### L'usage

J'utilise systématiquement le command pattern pour les raisons suivantes:

* les *command handler* s'inscrivent parfaitement dans les architectures hexagonales

* les *command handler* offrent de la cohérence sur la manipulation de ma couche "modèle“, ils en sont les uniques points d'entrée.

* les *command handlers* clarifient les frontières transactionnelles d'une opération métier. 

* les *command handlers* normalisent la couche de service applicative

* les *command handler* soulagent les prises de tête sur les Aggregate Root

J'abuse pas mal de ces *command handlers* car je les détourne parfois de leur but premier:

### L'abus

- Lorsque j'ai beaucoup d'inconnus, d'incertitudes, un manque de compréhension, ou simplement lorsque je suis fainéant ou pressé: je les utilise pour stocker du code métier temporairement, à vocation à être déplacé ultérieurement dans des services métiers ou des objets métiers.

- Lorsque je dois synchroniser plusieurs process métier entre eux: je les utilise au lieu d'implémenter des films d'horreur nommés [SAGA](https://blog.couchbase.com/saga-pattern-implement-business-transactions-using-microservices-part/#:~:text=The%20SAGA%20Pattern,data%20within%20a%20single%20service.)

- Lorsque l'implémentation d'un <abbr title="Aggregate Root">AR</abbr> est particulièrement subtile, difficile ou coûteuse avec Symfony/Doctrine, je code une partie de cette logique dans un handler (ça craint, vraiment à éviter).

*Cela doit sonner très faux aux oreilles des puristes, mea maxima culpa. J'ai fait mon deuil & je suis complètement dans l'acceptation car ça fonctionne très bien pour moi.*

### Le naming

Les commandes sont nommées d'après les processus métiers, les actions métiers qu'elles représentent. L'organisation s'adapte évidemment selon les besoins spécifiques:

```
src/Action/Checkout.php
src/Action/CheckoutHandler.php

src/Action/FraudulentPaymentDeclaration.php
src/Action/FraudulentPaymentDeclarationHandler.php

src/Action/Compliance/Registration.php
src/Action/Compliance/RegistrationHandler.php
src/Action/Compliance/FrenchEstablishmentRegistration.php
src/Action/Compliance/FrenchEstablishmentRegistrationHandler.php
```

> Maybe I would have used an imperative form for the Command: Register instead of Registration
>
> -- [Florian Klein](https://twitter.com/docteur_klein/status/1267008375646302214?s=20)

Un process métier, une action métier porte généralement un nom. Il est important de prendre le temps d'identifier clairement une action métier par son nom. Ce n'est pas toujours possible.

Un verbe est un indicateur d'une potentielle faiblesse de compréhension du métier, exemple: 

* `MarkAsRead`
* `ValidateStatus`

Attention, c'est une direction, dans mes dépôts il arrive très souvent d'avoir des commandes débutant par un verbe.


### Détail d'implémentation des commandes

Les commandes sont donc les expressions d'une intention (métier). Elles sont modélisées & implémentées comme de simple [DTO](https://en.wikipedia.org/wiki/Data_transfer_object) qui n'acceptent que des [primitives](https://en.wikipedia.org/wiki/Language_primitive#High_level_language_primitives) facilitant [l'architecture hexagonale](https://en.wikipedia.org/wiki/Hexagonal_architecture_(software)) ainsi que l'application [port/adapter](https://matthiasnoback.nl/2017/08/layers-ports-and-adapters-part-3-ports-and-adapters/). Les commandes représentent la frontière entre les primitives et les concepts métiers dans mon application:


```php
namespace Action\Compliance;

use Domain\Geo\Address;
use Domain\Geo\Country;

class Registration
{
  private string $userId;
  private string $name;
  private string $addressLine1;
  private string $addressLine2;
  private string $addressZip;
  private string $addressLocality;
  private string $addressCountry;

  public function __construct(
    string $userId,
    string $name, 
    string $addressLine1, 
    string $addressLine2,
    string $addressZip,
    string $addressLocality,
    string $addressCountry
  ) {
    $this->userId = $userId
    // ... boring stuff
  }

  public function getUserId(): Uuid
  {
    return new Uuid($this->userId);
  }

  public function getName(): string
  {
    return $this->name;
  }
  
  public function getAddress(): Address
  {
    return new Address(
      $this->addressLine1,
      $this->addressLine2,
      $this->addressZip,
      $this->addressLocality,
      new Country($this->addressCountry)
    );
  }
}
```

J'ajoute généralement de la validation Symfony sur les commandes pour gérer les erreurs "de forme" et avertir l'utilisateur. Là encore, via des annotations. En revanche, les règles métiers fortes sont toujours exprimées & implémentées dans les objets métiers (domain), une violation de ces règles entraîne systématiquement une levée d'exception.

Avec cette approche, l'implémentation de l'architecture hexagonale est enfantine:

* Les [Symfony/Form peuvent déclarer le DTO commande comme model](https://symfony.com/doc/current/form/use_empty_data.html), la fameuse data-class

* Les commandes (CLI) de Symfony/Console peuvent initialiser le DTO commande avec ces arguments

* Un Worker/Consumer RabbitMQ peut désérialiser un message et le transformer en DTO commande

* Un Symfony/EventListener peut transformer un Event en DTO commande

* Web Socket, etc...

En revanche, une fois que ma commande est transmise à son *Handler*, ce dernier manipule essentiellement des objets métiers (<abbr title="Value Object">VO</abbr>, Entity). Un objet métier fournit des garanties fortes: 

* Un état systématiquement valide (d'après les règles métiers)

* Un état quasi-systématiquement immuable (immutable). 

* Une API publique autorisant uniquement des changements d'état prévus & spécifiés par le métier

Manipuler ce type d'objet métier est un réel bonheur, un soulagement: la [charge cognitive](https://www.lilobase.me/la-complexite-une-histoire-de-charge-cognitive/) est très faible, inversement à la confiance dans le système, très forte. 

Quelques remarques :

* Pas de typage spécifique pour les ID des AR et des entités (UserId, BookId, ...). Pénible à maintenir avec Doctrine. Dans certains projets très complexes, il m'est arrivé d'en utiliser.

* Comment arbitrer entre VO (objet métier) et primitive?
Si le métier porte de l'importance à un concept, si le métier à des règles spécifiques liées à un concept : VO. Sinon, les primitives sont suffisantes. Cela reste du PHP, pas du Java.

### Les *command handlers* ?

Le rôle des *command handlers* est de coordonner AR, les Entités, en bref, de coordonner les concepts métiers entre eux.

Très souvent ils utilisent un `repository` pour hydrater l'AR concerné par l'action, la commande métier.

Le *handler* appelle ensuite des méthodes sur les objets métiers (AR/Entité). Doctrine implémentant le [data-mapping](https://www.doctrine-project.org/projects/doctrine-orm/en/2.7/tutorials/getting-started.html#what-is-doctrine), le *handler* gère également les cycles de la couche de persistance. En sus, certains *handlers* coordonnent d'autres détails d'infrastructure. Très souvent relatifs aux IO nécessaires à la composition d'un état métier correct (satisfaisant) répondant au pré-requis d'une action métier souhaitée.

Symfony/Messenger prend entièrement à sa charge la gestion des commandes et des handlers. Depuis un controlleur HTTP, une Symfony/Commande ou encore un Worker RabitMQ, il suffit d'injecter le bus et [de dispatch votre commande](https://symfony.com/doc/current/components/messenger.html#bus).

En cas de process métiers (trop) longs, passer les traitements des commandes en asynchrones ne nécessite quasiment aucun effort d'implémentation. Une [simple modification de la configuration de Symfony/Messenger](https://symfony.com/doc/current/messenger.html#transports-async-queued-messages): 
* la commande est envoyée via un RabbitMQ. 
* Le handler connecté en tant que "consumer" de la queue. 

Comme nous n'utilisons pas CQRS, une forte majorité des exécutions de commandes est synchrone, il suffit d'utiliser les fonctionnalités de [Symfony/Messenger pour récupérer le résultat d'un handler](https://symfony.com/doc/current/messenger/handler_results.html), ajouter un peu de sérialisation pour obtenir une belle représentation [<abbr title="Representational State Transfer">REST</abbr>](https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm) pour une réponse HTTP.   


## Les Bounded Contexts (BC)
Par défaut, je considère qu'un dépôt git représente 1 BC. Ma compréhension évolue souvent lors d'un projet. Ou tout simplement, le projet grandi, grossi et se découvre également de nouveaux BC's.

Il m'arrive très souvent de séparer des contextes a posteriori. La présence de plusieurs contextes peut signifier:

* une incompréhension du métier 
* de fortes incertitudes sur le périmètre métier
* une extrême complexité

### Organiser ses BC's
Les micro-services, c'est comme le CQRS: ça coûte un rein, si c'est mal orchestré: ça en coute deux. Si votre équipe dispose d'une armée de devops, vous avez de la chance. Sinon, le principe de localité, ça marche aussi:


```
src/BC1/Controller/
src/BC1/Command/
src/BC1/Domain/

src/BC2/Controller/
src/BC2/Command/
src/BC2/Domain/
```

Pourquoi? Séparer ou délimiter des BC's est une opération très définissante. Très risquée également. Synchroniser des BC's entre eux est relativement coûteux. 

De cette façon, vous pouvez faire évoluer vos BC's, itérer, comprendre, refactoriser et déplacer du code de *domain* en *domain*, de *BC* en *BC*. Lorsque vous atteignez la "maturité modèle" et maitrisez la compréhension métier. Vous avez toute la liberté de séparer vos BC sur plusieurs dépôts... et de jouer avec [<abbr title="kubernetes">K8s</abbr>](https://kubernetes.io/).

# Conclusion

Il est temps de prendre du recul à nouveau après cette plongée parfois très détaillée des techniques adoptées pour DDD & Symfony. Rappelez-vous le but de cet article et son message: "**Oubliez la tech & concentrez vous sur le métier**"

Ces détails techniques sont un condensé de connaissances, d'expériences, d'astuces ou de compromis rassemblés au fil des années pour gérer simplement une approche DDD en synergie avec l'écosystème PHP et Symfony. Mais encore une fois: c'est le moins important du DDD.

Ces méthodes sont à disposition dans **l'espoir de ne plus s'attarder sur le "comment" du code. Mais de concentrer les efforts & la valeur sur le "quoi" et le "pourquoi".**


**EDIT JUNE 2nd** 
Lucas Courot a réagi [via twitter](https://twitter.com/lucas_courot/status/1267596394107482113?s=20) sur 3 points: 
- les commandes asynchrones
- le coût de gherkin (Behat)
- les notifications en place des exceptions

Un échange de plusieurs tweets résume nos idées, opinions, divergentes ou convergentes, toujours intéressantes.