Le design pattern "Unit of Work"

Le design pattern Unit of Work est au coeur de nombreux projets du fait qu’il s’agit d’un modèle utilisé par de nombreux ORM (parmi lesquels on peut citer Hibernate pour Java, EntityFramework pour l’environnement Dotnet ou encore Doctrine pour PHP). Ce dernier a pour objectif de tenir à jour une liste d’objets et coordonne l’écriture des modifications en gérant les problèmes de concurrence.

Le cas des ORM (Object-Relational Mapping) est un cas d’école de l’utilisation de ce pattern. Lorsque l’on récupère des données (en base de données), qu’on les modifie, qu’on les supprime ou que l’on en insère des nouvelles, il est important de conserver une trace de ce qu’il s’est passé pour pouvoir ensuite les retranscrire dans la base de données.

L’avantage de l’Unit of Work est qu’il travaille par transaction. C’est-à-dire qu’il accumule les modifications pour ensuite les exécuter au moment voulu. De ce fait, si une erreur survient durant la transaction, toutes les modifications peuvent être annulées. Un autre avantage qui découle de la gestion transactionnelle est qu’il est alors possible de regrouper plusieurs opérations, réduisant ainsi le nombre d’accès à la base de données.

Dans sa version la plus simple, on pourrait définir une interface de travail comme celle-ci:

interface UnitOfWork
{
    public function registerNew(object $object): void;

    public function registerDirty(object $object): void;

    public function registerDeleted(object $object): void;

    public function commit(): void;
}

Et dont l’implémentation la plus simpliste pourrait ressembler à:

class UnitOfWorkImplementation implements UnitOfWork
{
    private array $newObjects = [];
    private array $dirtyObjects = [];
    private array $deletedObjects = [];

    public function __construct(
        private readonly Connection $db,
    ) {}

    public function registerNew(object $object): void
    {
        $this->newObjects[] = $object;
    }

    public function registerDirty(object $object): void
    {
        $this->dirtyObjects[] = $object;
    }

    public function registerDeleted(object $object): void
    {
        $this->deletedObjects[] = $object;
    }

    public function commit(): void
    {
        $this->db->beginTransaction();

        try {
            foreach ($this->newObjects as $object) {
                $this->insertIntoDb($object);
            }

            foreach ($this->dirtyObjects as $object) {
                $this->updateDbData($object);
            }

            foreach ($this->deletedObjects as $object) {
                $this->deleteFromDb($object);
            }

            $this->db->commitTransaction();

            $this->newObjects = [];
            $this->dirtyObjects = [];
            $this->deletedObjects = [];
        } catch (Exception $e) {
            $this->db->rollbackTransaction();

            throw $e;
        }
    }

    private function insertIntoDb(object $object): void
    {
        // ...
    }

    private function updateDbData(object $object): void
    {
        // ...
    }

    private function deleteFromDb(object $object): void
    {
        // ...
    }
}

C’est un des pattern décrit dans l’excellent livre de Martin Fowler Patterns of Enterprise Application Architecture (lien non affilié) qui date de 2002 mais qui contient l’essentiel des design patterns les plus populaire de nos jours.