Aller au contenu

DÉVELOPPEMENT

Legacy to DDD : Part. 1 - Comment transformer son code existant en utilisant des concepts concrets de Domain Driven Design

Mise à jour : 9 Nov. 2023

Publié le : 20 Février 2023

38 min.

Partager

L’utilisation d’outils pour améliorer la façon de développer des projets informatiques est la clef pour obtenir des résultats adaptés aux différents besoins priorisés d’un projet : coût, maintenabilité, rapidité, lisibilité, performance, évolutivité …

Parmi eux, le Domain Driven Design (que nous nommerons DDD) améliore considérablement la qualité, la lisibilité et la testabilité sur le long terme, au détriment du temps de mise en place. Le postulat de base du DDD est simple : toute programmation devrait refléter la réalité du domaine (ou métier) avant toute considération technique.

Que faire si on réalise après coup que cette technique aurait sûrement été précieuse pour un projet ? Le passage d’un projet legacy en DDD est-il si coûteux ? Voyons cela ensemble par un exemple concret. L’idée est ici est de partir d’un projet existant, d’y apporter les modifications, en pointant le point de vue DDD.

Disons qu’un client vient me demander de moderniser une application existante en php 7.1 (dernière release le 24 octobre 2019), basée sur le framework Cakephp en version 3.1. Prenons en exemple la démo de Cakephp : Ouvre une nouvelle fenêtrehttps://github.com/cakephp/bookmarker-tutorial. Elle n’a pas été touchée depuis 2017. Je l’ai forkée pour l’occasion ici : Ouvre une nouvelle fenêtrehttps://github.com/vibby/cakephp-bookmarker-tutorial

Attention : Appliquer le DDD à cette application n’est sûrement pas très pertinent. Elle ne présente pas de forte valeur métier. Mais elle a l’avantage d’être facile à comprendre et disponible en open-source ^^ Parfait pour un exemple !

Par souci de lisibilité, j’ai systématiquement placé les propriétés en public dans les différentes classes. À ne pas faire sur un vrai projet !

Notre premier objectif sera d’identifier et d’extraire toutes les actions métier et de les mettre à part dans la toute nouvelle couche domaine de cette application. Ensuite, nous aborderons le chemin de la modernisation, et même celui de l’extension.

Préparer le terrain

Faire fonctionner l’appli existante

Quand on a récupéré ce code, on va tâcher de le faire tourner en local. On peut voir qu’il y a de la configuration Ansible et Vagrant dessus. Mais comme nous sommes joueurs, je vous propose plutôt de passer par un petit docker-compose pour mettre ça en place ;)

Je vous épargne la conf de tout ceci, mais vous trouverez tout dans ce commit : Ouvre une nouvelle fenêtrehttps://github.com/vibby/cakephp-bookmarker-tutorial/commit/a4f354a2a6a049eac909ec9ee0008909f1316f0a

On lance le tout.

On créé et on rempli la base de données

On peut déjà se connecter sur Ouvre une nouvelle fenêtrehttp://0.0.0.0:9050/users/login avec user@example.com et «password».

Pour chaque section, vous trouverez le parcours sur un historique git. En voici la première étape : Ouvre une nouvelle fenêtrehttps://github.com/vibby/cakephp-bookmarker-tutorial/commit/a4f354a2a6a049eac909ec9ee0008909f1316f0a

Laisser de la place à nos nouveaux outils

La prochaine tâche consistera à faire de la place parmi les sources du projet pour de nouveau éléments. On va donc dédier un espace du dossier ./src pour les sources pré-existante.

Puis on va modifier la résolution des domaines de noms pour qu’il trouve ces affaires au bon endroit.

Nous allons conserver le domaine de nom *App* pour la partie Cakephp, pour ne pas avoir a le modifier partout dans l’existant. À noter qu’il faut aussi préciser à Cakephp où trouver ses templates et ses traductions.

Le commit : Ouvre une nouvelle fenêtrehttps://github.com/vibby/cakephp-bookmarker-tutorial/commit/7121450d3f51d802714368ab93d4390b0ff1080a

Commencer par un bout : afficher un bookmark

Commençons pas un bout, le contrôleur BookmarksController::view. Vraisemblablement, il sert à afficher un bookmark, il faut donc le retrouver. Confions cette responsabilité à la couche métier !

Avant de modifier le contrôleur, créons le parcours de l’action coté domaine.

DDD : C’est parti !

En DDD, tout action faite au métier entre par une «porte» bien identifiée : l’« entrypoint ». Il se présente sous la forme d’un objet qui explicite l’intention métier. Créons cet objet pour l’action de retrouver un bookmark.

Cet entrypoint ce place dans la couche « application » qui sert à faire l’interface entre l’infrastructure (cakephp dans notre cas), et la couche du domaine (ou métier).

Puis nous créons un handler capable de la traiter.

Maintenant, le repository pour aller chercher la donnée.

Vous remarquerez que ce n’est qu’une simple interface, nous mettrons son implémentation sur pied tout à l’heure, côté Cakephp. Et finalement, voici enfin l’objet métier !

OK, je l’ai simplifié au max pour vous faciliter la lecture :) À ne pas faire sur une vraie prod !

N’oublions pas de faire connaître cette nouvelle partie à l’autoloader.

Puis mettre à jour ses résolutions.

Résumé : Ouvre une nouvelle fenêtrehttps://github.com/vibby/cakephp-bookmarker-tutorial/commit/064c6a84ac65753746ad9ae273b056857402c033

Cette action est très simple, elle répond au besoin rudimentaire d’un affichage. En DDD, c’est le domaine qui décide de ce points d’entrées. Il n’est pas question ici de faire un simple CRUD. Mais un point d’entrée à un véritable sens pour le métier. Ce qui est tout à fait possible pour ce point de lecture d’un bookmark.

Appel depuis le contrôleur Cakephp

Quand on essaye de faire appel à la couche métier depuis Cakephp, on se heurte à un problème : Cakephp n’a pas de mécanisme de container pour gérer les dépendances entre les classes. Qu’à cela ne tienne, nous allons créer notre propre container sur mesure. Utilisons pour cela le concept de composant de Cakephp

Définir chaque service individuellement sera sûrement un peu fastidieux, mais c’est le prix a payer pour faire cohabiter des paradigmes aussi éloignés que la philosophie de Cakephp et l’injection de dépendance.

En paramètre du constructeur de GetBookmarkHandler, nous avons donné le composant BookmarkRepositoryComponent. Il doit implémenter l’interface BookmarkRepository.

Dans la dernière partie, on construit l’objet métier à partir de l’entité Cakephp, car c’est le contrat exigé par la couche domaine.

Maintenant, il nous faut importer le composant Container dans la fonction initialize du contrôleur.

Et nous pouvons nous en servir dans le contrôleur.

Là, on vient de faire exactement le chemin inverse qu’auparavant : passer de l’objet métier à l’entité Cakephp, puisque c’est sa façon de travailler.

On peut constater que la partie Cakephp ne remplit aucune autre fonction que l’interprétation de la requête HTTP et le rendu d’un retour. C’est précisément ce que l’on attend de la couche infrastructure. Les actions métiers (sur les données) sont déléguées à la couche domaine.

Les modifications : Ouvre une nouvelle fenêtrehttps://github.com/cakephp/bookmarker-tutorial/commit/33595707c4f7e2dc3cf09b064fe2d6710129ac9c

Et voilà ! Notre application passe maintenant par une couche domaine indépendante pour manipuler nos données ! Il n’y a pour le moment aucun intérêt, on a même perdu des fonctionnalités :) Mais progressons dans cette voie !

Iso-fonctionnel

Sur le site de l’application, à la page d’un bookmark, on peut voir qu’il manque deux éléments importants : l’utilisateur «propriétaire» et les tags associés. Il va nous falloir les ajouter en tant que modèles.

Et ajoutons ces propriétés au bookmark

Malheureusement, nous ne pouvons pas typer ces propriétés : cette possibilité n’existe que depuis PHP 7.4. Nous reviendrons dessus.

Il nous reste a demander à l’ORM de Cakephp d’aller chercher ces données et de les hydrater.

Et le chemin retour, quand on revient au contrôleur.

Nos tags et notre utilisateur s’affiche maintenant correctement.

Par ici le commit : Ouvre une nouvelle fenêtrehttps://github.com/vibby/cakephp-bookmarker-tutorial/commit/5f592d8bbc02a4e709d2184a7d1822447008d3f8

Petite refactorisation

Nous avons finalement deux systèmes pour représenter les mêmes données. Il y a un objet bookmark côté domaine (Model), mais également côté Cakephp (Entity).

Avec un peu d'expérience, on sent bien qu’on va passer beaucoup de temps à transformer nos données d’un système à l’autre, alors outillons nous pour ce boulot !

La profondeur de transformation de la donnée sera directement liée au données que l’on a de présente au départ. Par exemple, si l’utilisateur n’est pas fourni au départ, il restera vide.

Ici, j’ai décidé de séparer les différentes entités dans des transformateurs différents. Voici celui d’un tag.

Et enfin l’utilisateur

Il nous reste maintenant à nous en servir dans les parties concernées : le contrôleur et le repository

Voilà qui simplifie les choses et les rend plus lisibles. Retrouvez le détail de ces modifications ici : Ouvre une nouvelle fenêtrehttps://github.com/vibby/cakephp-bookmarker-tutorial/commit/b60acf7e0dd636b3f290c1f68826ee1c8043c6a6

Point d’étape

Nous avons donc le même résultat fonctionnel, tout en passant par notre couche domaine. Ce qui signifie que pour l’utilisateur, la refonte est parfaitement transparente ! Il est donc tout à fait possible de fusionner ces modifications au reste de la base de code, et d’envoyer tout ça en production.

Ce que nous sommes en train de faire là, c’est de la refonte en continu. Sans interruption de service, sans avoir à passer 1 ou 2 ans dans une refonte avant de pouvoir livrer. Pour un décideur ou un chef de projet, il y a là une grande valeur.

Dans notre cas, nous avons à faire à une simple mise à jour des données, c’est pourquoi la porter dans la couche domaine n’a pas été complexe, et aussi qu’elle n’a apporté aucune plus-value. Mais pour une application qui mérite de passer au DDD, le processus de porter les règles métier à la couche domaine permet de les identifier clairement, de les faire comprendre et valider par l’équipe, et de les modifier simplement si besoin.

Pour être plus concret, voici quelques exemple de règle métier complexes :

  • Une réduction sur un e-commerce qui s’applique sur les produits d’une catégorie à condition d’atteindre une certaine somme dans cette catégorie, et uniquement pour les clients n’ayant jamais commandé dans cette catégorie
  • Une règle comptable pour définir si la TVA peut être décomptée ou non d’une facture
  • Un retour d’erreur d’un diagnostic OBD sur un véhicule automobile

Modifier un bookmark

Une opération de lecture a été faite. Mais une opération d’écriture sera peut-être un peu plus complexe ? Attaquons-nous à la modification d’un bookmark.

Commencer par le domaine métier

À nouveau, on va commencer par implémenter le domaine métier.

Cela n’a rien d’un choix éditorial, mais ça correspond bien à la philosophie DDD : le domaine métier doit être au cœur de l’application. Elle est prioritaire et doit se suffire à elle-même. C’est à la partie infrastructure de s’adapter pour adhérer au domaine.

Créons donc l’input puis le handler.

On fait ici appel à un updater qui va véritablement appliquer les modifications au bookmark.

Son utilisation peut sembler superflu pour le moment, mais il se rendra très utile prochainement.

Il nous faut mettre en place la mise à jour des données. Nous le faisons dans le repository pas simplicité. Mais il serait pertinent d’avoir une autre interface pour les écritures (

)

Petit commit : Ouvre une nouvelle fenêtrehttps://github.com/vibby/cakephp-bookmarker-tutorial/commit/f4aea58629c5dc793e9e241a2ab6c4fc55243d88

La mise à jour côté infra Comme nous l’avons fait pour la vue, on va faire appel à la couche métier pour les interactions avec les données

Il nous faut implémenter aussi la mise à jour des données.

Nous avons bien fait de mettre à part notre système de transformation des objet du système Cakephp au domaine métier, car nous l’avons utilisé pas moins de 5 fois déjà !

Nous avons créé de nouveau services qu’il nous faut préciser au container.

Le commit de cette partie : Ouvre une nouvelle fenêtrehttps://github.com/vibby/cakephp-bookmarker-tutorial/commit/355bc6e08aa9cc9d21116032be85a0956b433b1c

À présent, nous avons une interface opérationnelle pour la modification d’un bookmark, mais il manque une fonctionnalité ;)

Les entités liées

Nous avons traité le cas simple ici, mais nous avons écarté la liaison entre bookmark et tag. Il y a ici une spécificité métier qu’il va nous falloir implémenter : la liste des tags est donnée sous la forme d’une liste de titres. Quand un titre inexistant parmi les tags est trouvé, il faut créer ce tag. C’est une action qui est faite pour le moment dans la classe App\Model\Table\BookmarksTable

Nous allons mettre à profit notre BookmarkUpdater pour y matérialiser cette logique.

Ce code n'est pas optimisé, puisqu’on fait autant d'appels à la base de données que de titres de tag. Je l’ai fait ainsi pour en faciliter la lecture. Nous avons fait appel à un nouveau repository ici.

En remontant la chaîne, on ajoute ce paramètre au handler.

Puis au tour de l’input

En résumé ici : Ouvre une nouvelle fenêtrehttps://github.com/vibby/cakephp-bookmarker-tutorial/commit/4ecad97fb7f82f7951bc956cda763172484c0047

Tags sur la couche infra

Côté contrôleur, il nous faut préciser la liste des titres de tags passés en paramètre.

Et nous avons introduit un tout nouveau TagRepository que nous implémentons maintenant

Pour parachever le tout, ne pas oublier de mettre à jour notre service avec sa nouvelle dépendance.

Et puisque nous l’avons transféré côté métier, nous pouvons débrancher la fonctionnalité côté Cakephp en supprimant les méthodes

et
de la classe
.

En voici le résumé : Ouvre une nouvelle fenêtrehttps://github.com/vibby/cakephp-bookmarker-tutorial/commit/4184cb19417b389b069ad941040aa8a5e2f314ef

Nous avons maintenant un cas complet d’écriture en passant par la couche du domaine. Comme pour la vue, nous sommes iso-fonctionnel avec l’existant. Nous pouvons pousser en prod ^^

Point d’étape

Nous avons vu deux cas de base d’une application web : récupération d’une donnée et modification d’une donnée. Sur la base de cette approche, toute action pourra être maintenant réalisée. Et toute règle métier pourra être implémentée du côté domaine. Nous voici parés d’un bon pied pour moderniser cette petite application.

Nous pourrions maintenant passer en revue toutes les actions des contrôleurs pour leur appliquer le même sort. Mais je vous laisse cela en travaux pratiques :) Les MR sont les bienvenues sur le repo.

Si vous avez surement remarquer que les dépendances ne se font que dans un seul sens : l’infrastructure utilise le domaine, mais le domaine reste bien indépendante la couche infrastructure.

Il nous restera cependant quelques épines. Il restera côté Cakephp des règles métier qui devraient trouver leur place dans le domaine. Je pense notamment à la validation des entités. Elle passe aujourd’hui par la classe

, notamment à travers la méthode
. Il nous faudra bien transférer ces règles dans le domaine. Et d’une manière générale, il faudra dépouiller toutes les classe finissant par
, à l'exception de la méthode
.

Un autre point tout à fait remarquable aussi : Cakephp n’est pas le framework le plus moderne, et pourtant, on peut l’adapter pour cet exercice. On conserve ainsi la simplicité d’un framework « rapide », tout en apportant la puissance d’un métier solide. On peut même envisager de conserver ce framework dans le cadre de cette modernisation, Nous verrons cela dans un prochain article que nous publierons prochainement !

Vincent Beauvivre

Développeur back

Partager

Articles similaires