Le pattern "Commande"

Cet article a été publié depuis plus de 6 mois, cela signifie que le contenu peut ne plus être d'actualité.

Si je reprends la définition de wikipedia, il s’agit d’un patron de conception (ou design pattern en anglais) qui permet d’encapsuler la notion d’invocation. C’est un moyen très utile de rendre votre code modulaire et lisible en découpant votre code en différentes commandes, chacune ayant des responsabilités uniques et spécifiques.

Un objet Command est un DTO (Data Transfer Object) qui permet d’encapsuler toutes les informations nécessaires pour réaliser une action. La commande n’a aucune logique, elle s’utilise au travers d’un bus (command bus pattern) qui est chargé de recevoir une commande valide et de déclencher les actions associées. Son unique responsabilité est de gérer les différents cas d’utilisation en déléguant l’exécution de la commande à une classe spécifique au traitement en question. L’objectif est de permettre au développeur d’émettre une intention d’effectuer une action sans que ce dernier n’est à se soucier de la manière dont elle va être traitée et/ou implémentée.

<?php

class CreateCustomer {
    public function __construct(
        public readonly string $name,
        public readonly string $phonenumber,
    ) {
        // assert data validity
    }
}

$this->bus->execute(new CreateCustomer(/* ... */));

Il existe de nombreux composants PHP qui vous permettent d’implémenter ce pattern dans vos projets. Mais concevoir un bus de commande n’est fondamentalement pas compliqué. Dans sa version minimaliste, il s’agit de faire un lien entre une commande et son gestionnaire (ou handler en anglais).

<?php

class CommandBus {
    /** Handler[] */
    public function __construct(
        private readonly array $handlers,
    ) {
    }

    public function execute(object $command): void
    {
        $commandClass = get_class($command);
        $handler = $this->handlers[$commandClass] ?? throw new RuntimeException("No handler for $commandClass command.");

        $handler($command);
    }
}

class MyCommand {}
class MyCommandHandler
{
    public function __invoke(MyCommand $command): void
    {
        echo "Hello World", PHP_EOL;
    }
}

$bus = new CommandBus([
    MyCommand::class => new MyCommandHandler(),
]);
$bus->execute(new MyCommand()); // will display "Hello World"

Si ce type d’architecture a été popularisé par la montée en puissance du Domain Driven Design ou des architectures CQRS, il est tout à fait possible de l’utiliser dans n’importe quel projet.

J’en suis pour ma part très friant car il y a de nombreux avantages à utiliser ce paradigme. Il permet de bien découper le code, d’avoir des petites classes avec des responsabilités uniques et isolées, donc facile à tester. Au delà de ces avantages purement technique, cela permet également de pouvoir dresser simplement la liste de cas d’utilisation qui sont gérés par notre projet.

Un autre aspect que j’apprécie particulièrement est la facilité que l’on a d’étendre le comportement d’un bus de commande. Il est possible de décorer un bus afin de lui ajouter des comportements supplémentaires qui ne sont pas directement liés à l’intention métier que l’on souhaite réaliser dans l’application. Il sera par exemple possible de créer un bus de commande qui gérera les transactions de base de données, de gérer des logs ou d’effectuer diverses tâches de monitoring, tout cela de manière complètement découplée de votre code orienté métier.