Aller au contenu

DÉVELOPPEMENT

Legacy to DDD : Part. 2 - Comment moderniser votre application PHP avec le Domain-Driven Design

Publié le : 8 Mars 2023

34 min.

Partager

Cet article fait suite à notre précédent article sur le DDD et son application à un projet vieillissant, réalise en CakePHP. Dans la première phase, nous avons passé deux actions essentielles en mode DDD : une lecture et une écriture. C’est le bon moment pour creuser les avantages et fonctionnalités que nous offre le DDD !

Pour retrouver les parties 1 à 3 : (/blog?category=developpement)

4. Aller plus loin dans le DDD

Avant d’aller plus loin, regardons de plus près ce que nous avons fait là.

4.1. Value-object

Considérons par exemple la signature de notre bookmarkUpdater

Pas facile de faire confiance à cette variable

pour détenir réellement une adresse web. On ne sait d’ailleurs pas si le protocole est http/s ou un autre. Et si on remplacait cette simple chaîne de caractère en objet métier qui portera le sens de ce qu’elle est !

En DDD, on le nomme value object. C’est un objet métier, avec toute la valeur que cela implique. Mais à la différence des autres objets métier (comme le bookmark), il n’a pas d’identité propre.

Je n’ai pas pu résister à y ajouter une validation minimum, avec cette regex trouvée sur stackoverflow que je n’ai même pas testée :P. Au passage, j’ai ajouté cette petite exception toute simple.

Il nous faut aussi réaliser la création du value object quand on récupère les données de la persistance via le repository. On réalise à ce moment que lorsque l’on fait remonter des données depuis la persistance, on passe aussi par la validation. Ce n’est pas pertinent car les règles de validations peuvent changer dans la vie du projet, et on peut se retrouver avec des données stockées invalides. On va donc s’arranger pour créer la value-object quand même.

On pourrait même envisager d’ajouter une validation des données même quand on récupère les données de la base de données. Par exemple, pour éviter un

. Ok, la couche infrastructure est censée éviter ces injections, mais il est bon de savoir qu’on peut faire des vérifications ici. Il nous faut maintenant ajouter cette logique à la transformation des objet métier Cakephp vers le modèle métier

Et enfin, nous l’avions oublié, la mise à jour de l’appel à l’updater

Je vous conseille vivement de créer les value object le plus tôt possible (dans les handler), et de ne travailler qu’avec ces objets dans toute la partie domaine. Franchement, c’est très confortable de travailler avec des objets que l’on connaît et en qui on peut avoir confiance.

La value-object est tout à fait disposée à recevoir toute validation spécifique à un projet. Par exemple, un value object « pourcentage » pourra valider que sa valeur est bien comprise entre 0 et 100. Ou bien des règles encore plus dirigées par le métier, comme une commission qui ne doit jamais 10%.

Toutes les propriétés peuvent ainsi être adaptées sous la forme de value-object : les chaînes de caractères, les entiers et décimaux, des tableaux… On peut même y stocker des données composées, comme une adresse postale composée d’un numéro, nom de voie, code postal, etc. C’est en fait un choix métier de faire ce regroupement. Et ce sera à la couche infra de se débrouiller pour la stocker, puis de la recréer depuis la persistance.

Une des régles importantes du DDD, c’est que toute classe métier créée doit être valide. Dans tout les cas on doit pouvoir compter sur une base minimale de logique en son sesin. Ceci est valable pour les objets métier (le bookmark) ou les value-object. Cependant, c’est le métier lui-même qui défini ce qu’est un objet valide.

Suivi des modificactions : Ouvre une nouvelle fenêtrehttps://github.com/vibby/cakephp-bookmarker-tutorial/commit/aff719ccb6cb8e9e473d2411db331630ee99596c

4.2. Validation de l’input

Nous venons de mettre en place un niveau de validation basique au niveau de value-object. Mais il y a d’autres validations qu’il nous faudra mettre en place. La couche domaine de l’application ne fait confiance à personne. Il nous faut donc mettre en place une validation des demandes qui y sont faites. Voyons donc la validation de input de mise à jour d’un bookmark.

On placera ici toute sorte de validation qu’on est en mesure de coder. On peut aussi créer des règles de validation basées sur plusieurs champs. Par exemple, la description est obligatoire si le titre fait moins de 10 caractères. Oui, les règles métiers ont parfois leurs raisons que la raison ignore ! Il nous faut maintenant exécuter cette validation en tête du handler.

En plus de lever l’exception en cas d’invalidité de l’input, nous ajoutons aussi le cas où le bookmark est introuvable. Par exemple, si il a été supprimé par un autre utilisateur entre temps.

On a créé ici cette exception qui va se charger de porter la liste des erreurs à la mise à jour, auquels nous avons donné le nom de violation.

À ce stade, notre application aboutira à une erreur 500 si on essaie de modifier le titre d’un bookmark par « ab » car elle ne respecte pas la règle des 3 caractères. Du point de vue du domaine, la règle métier est respectée, la fonction est remplie.

Il va donc falloir s’adapter côté infrastructure pour en rendre compte correctement à l’utilisateur. Pour cela, nous allons pouvoir intercepter cette exception.

N'oublions pas d’injecter le validateur via notre container maison.

Et nous voici avec de magnifiques messages d’erreur sur la validation qui proviennent directement de la couche domaine. Nous pouvons retirer les validations faites côté Cakephp, dans la classe BookmarkTable.

Le résumé précis dans ce commit : Ouvre une nouvelle fenêtrehttps://github.com/vibby/cakephp-bookmarker-tutorial/commit/8fa09557b8914d8b55a04b17b37eec888cf943cb

Nous avons mis en place ici la validation sur la base de l’input, mais on pourrait utiliser la même technique pour faire de la validation au niveau de l’objet métier avec des règles différentes. Par exemple, pour ce point d’entrée, on pourrait refuser que l’url soit sur un autre protocole que http. Mais pour l’objet lui-même, il pourrait autoriser aussi ftp ou tout autre protocole.

4.3. Validation basée sur le contexte

Pour aller plus loin, on pourrait avoir des règles de validations basées sur le contexte global. Par exemple, le nom de domaine en cours, ou l’utilisateur connecté. Commençons par le validateur lui-même.

Nous utilisons un nouveau service ici pour retrouver l’utilisateur courant.

Il s’agit d’une interface, car nous laissons son implémentation pour l’infrastructure, dans le cadre d’un composant Cakephp.

Ajoutons tout de suite ces dépendances à notre container.

Et bien il ne nous reste plus qu’à appliquer ce nouveau validateur au handler.

Ainsi les erreurs de notre nouveau validateur vont s’ajouter aux autres erreurs déjà existantes.

Nous sommes maintenant en mesure d’établir des règles de validations basées sur toute sorte d’élucubration que le métier voudra voir implémenter !

Il nous reste une petite chose à faire cependant, c’est de transmettre les erreurs de validations à la création des value-object pour les afficher à l’utilisateur.

Le résumé en code ici : Ouvre une nouvelle fenêtrehttps://github.com/vibby/cakephp-bookmarker-tutorial/commit/ee34d69633084c34c1094217f6a37558502a2e57

Maintenant que nous avons un peu de code dans le domaine, il est clair que ce cœur de l’application est la partie la plus cruciale de l’application. La tester unitiairement est donc sûrement une très bonne idée. Gardons cette tâche pour le chapitre 5.6, quand nous aurons une gestion des dépendances plus complète.

4.4. Langage ubiquiste

Dans le projet sur lequel nous travaillons ici, les termes utilisés sont Bookmark, Url, Tag, etc. Mais il pourrait survenir que ces termes ont été choisis par le développeur au moment de réaliser concrètement les implémentations.

Mais si on parle avec d’autres acteurs du projet, on va peut-être réaliser qu’il a un décalage de vocabulaire entre les acteurs. Ce décalage est source d’incompréhensions et donc d’erreurs. D’ailleurs, ce n’est pas les idées qui forment les mots, mais bien les mots qui forment les idées.

C’est pourquoi le DDD nous propose d’utiliser un langage ubiquiste, c'est-à-dire universel tous les acteurs du projet. Ça peut donner parfois des résultats étranges, notamment quand le métier parle en français. On se retrouve avec du bon franglais dans le code, comme une méthode getSociete ou bien une classe CreerSocieteHandler. Mais le gain en efficacité du projet en réel !

Le langage ubiquiste, unviversel pour le projet, participe à éviter de créer le décalage entre ce que disent les acteurs du projet, et ce que l’on peut lire dans le code.

5. Assurer l’avenir

5.1. Montée de version

Il est temps de passer à la mise à jour de toutes ces vielles sources. On va passer à la dernière version de Cakephp en deux étapes.

On se retrouve avec un bon paquet de dépréciations relativement facile à traiter. Je vous passe les détails que vour pourrez consulter dans ce commit : Ouvre une nouvelle fenêtrehttps://github.com/vibby/cakephp-bookmarker-tutorial/commit/ee69151acdce62c80da599580bd4ac883c0c6082

Passons à php 7.4 et composer 3 pour installer cakephp 4 dans de bonnes conditions.

Pour mettre à jour notre base de code, nous allons utiliser l’outil Ouvre une nouvelle fenêtreRector, c’est un outil d’automatisation de la modernisation du code lorsqu’il n’y a pas d'ambiguïté. Par exemple, si une classe implémente une interface, il va mettre à jour les signatures des méthodes de la classe, identiques à celles de l’interface.

D’ailleurs, Cakephp nous propose un outil pour automatiser cette montée de version, qui se base sur Rector.

On peut maintenant supprimer l’outil de montée de version

Nous voilà prêt pour le passage sur cakephp 4.4 à proprement parler, la dernière en date.

Et puisque nous sommes si bien lancés, passons aussi à la dernière version de PHP.

On remplit à nouveau la base de données

On trouve alors des erreurs liées au fort typage introduit dans cakephp 4. Malgré ses efforts, Rector ne les a pas toutes réglées. Pour y palier, il nous suffit de mettre à jour les fichiers de cakephp qui sont versionnés mais qui ont évolués, à partir de Ouvre une nouvelle fenêtrehttps://github.com/cakephp/app/tree/4.x/src. C’est le cas notamment des fichiers

,
,
, etc.

Autre chose : les fichiers des template changent d’extension et passent de .ctp à .php ce qui me semble bien plus naturel puisque ce sont précisément des fichiers php. Et cakephp propose de les placer plutôt à la racine du projet. Il y a encore quelques petits ajustements, je vous laisse le loisir de voir cela dans ce commit : Ouvre une nouvelle fenêtrehttps://github.com/vibby/cakephp-bookmarker-tutorial/commit/43519dd08804ec6637e0b52ef9c58a6ad0aebc38

Finalement, on revient rapidement à notre application fonctionnelle !

On récolte ici le fruits de nos efforts ! La séparation des couches de l’application nous a permis une montée de version de l’infrastructure simplifiée.

Si vous en avez déjà fait, vous savez que c’est un travail long et fastidieux. En minimisant la couverture de responsabilité de la couche infra, on simplifie grandement son évolution et sa maintenance. On touche du doigt un des énormes intérêts du DDD. D’ailleurs, dans l’interface de l’application, on ne rencontre pas de dysfonctionnement pour les fonctionnalités que nous avons passées en couche infra + application + domaine. Mais le reste des fonctionnalités rencontre des exceptions. Par exemple, la création d’utilisateur qui aboutit à l’erreur

.

5.2. Dépendances et php-arkitect

Nous l’avons déjà précisé, les dépendances entre les couches de l’application ne se font que dans un seul sens :

L’intérêt de cette contrainte, c’est que le domaine conserve une bonne indépendance de l’infrastructure. Et en cas de modification au niveau de l’infrastructure, elle ne devrait jamais impacter le domaine. En voici quelques exemples :

  • mise à jour du framework (comme on vient de le voir)
  • mettre en place un moteur de recherche pour indexer les données
  • utilisation d’un système de messaging en asynchrone pour l’envoi d’email

Par contre, une modification au niveau de la couche domaine peut tout à fait avoir des répercussions au niveau de l’infrastructure. Comme par exemple l’ajout d’un paramètre à un entrypoint. Pour s’assurer que cette règle restera respectée dans le temps, nous allons mettre en place un outil d’analyse : Ouvre une nouvelle fenêtrephpaskitect.

Écrivons maintenant la règle toute simple que la couche domaine ne doit dépendre d'aucun autre domaine de nom.

On lance phpakitect avec la commande suivante

C’est un échec, phparkitect nous a trouvé deux violations.

Puisque nous avons défini que nous ne voulions aucune dépendance, il a identifié la classe ```\Exception``` et l’interface ```\Throwable``` comme des dépendances extérieures. Nous allons donc les inclure parmi les classes autorisées.

On a le même souci avec la dépendance à ```LogicException``` depuis la couche application. Cette identification a la vertu de garder une vision claire et nette des dépendances de la couche domaine. Vérifions aussi les dépendances de la couche application.

Tant que nous sommes dans les règles, laissez moi vous montrez comment phparkitect peut aussi nous aider un code propre côté framework avec les règles suivantes.

La syntaxe de phparkitect parle d’elle-même que j’ai à peine besoin de paraphraser : *Toute classe dans le nom de domaine

devrait avoir un nom qui ressemble à
pour garder une cohérence dans le nommage des composants.

Nouveau commit : Ouvre une nouvelle fenêtrehttps://github.com/vibby/cakephp-bookmarker-tutorial/commit/5a522f3efee6b04356044af413052737d3ef1ea1

5.3. php-cs-fixer

Php-cs-fixer est un outil de formatage du code, en respect des standards et des normes, notamment les PSR-1 et 2. Il s’installe dans un dossier à part afin d’éviter des conflits inutiles entre dépendances, puisque c’est un outil indépendant.

Puis on lance la correction automatisée

Il nous a trouvé une bonne vingtaine de corrections à réaliser. En voici un exemple :

Il ne réalise aucune modification qui puisse présenter un risque pour le fonctionnement de l’application. Les règles que php-cs-fixer applique sont discutables sur le plan de l’efficacité, mais elles ont l’énorme avantage d’uniformiser tout code. On se sent toujours plus à l’aise dans un format que l’on retrouve ! On va le configurer pour lui demander d’aller un peu plus loin, en utilisant toutes les règles sous le nom «PhpCsFixer».

Puis on relance la correction automatisée sans préciser l’emplacement des sources puisque nous l’avons défini dans le fichier de configuration.

Il nous a trouvé à nouveau 25 corrections. On envoie tout ça dans un nouveau commit : Ouvre une nouvelle fenêtrehttps://github.com/vibby/cakephp-bookmarker-tutorial/commit/3584c595b9472d2b557e011ba716b317af947175

5.4. phpstan

Ajoutons maintenant un autre outil d’analyse statique qui tâche d’identifier de mauvaises pratiques au sein de l’application.

Et lançons le.

Une seule erreur est identifiée.

Et effet, c’est un reliquat de la façon de fonctionner du système d’autorisation de cakephp. On va dire que les utilisateurs ne sont pas autorisés par défaut pour le moment.

Phpstan offre 10 niveaux de vérification, de 0 (par défaut) à 9. Laissons de côté le code lié à Cakephp et lançons l’analyse sur les parties domaine et application uniquement.

Joie ! Voilà de quoi améliorer notre code !

Prenons une erreur en exemple.

L’erreur est limpide, la propriété

de l’objet
n’est pas typée. Et pour cause, ce n’était pas possible dans notre php 7.1 original. Les erreurs rencontrées sont en fait de 4 types :

  • Pas de typage pour une propriété
  • Pas de typage pour un paramètre de méthode
  • Pas de typage de retour de fonction
  • Pas de typage pour les éléments d’un tableau

En fait, phpstan va plus loin que les contrôles de php pour le typage et donc la cohérence globale de l’application. Et c’est bien ! Traitons tous ces points.

Si on lance l’analyse sur la partie infra maintenant.

Il y a là bien plus de travail, nous n’allons pas les traiter. Mais on constate avec bonheur que les parties que nous avons passées sous le paradigme du DDD ne sont que très faiblement touchées ! Encore une bonne raison d’avoir sauté le pas.

Je vous épargne les détails des corrections, mais vous les trouverez dans le commit : Ouvre une nouvelle fenêtrehttps://github.com/vibby/cakephp-bookmarker-tutorial/commit/f8788dc6f3b95389fd5a1c5de9a8201c69092268

5.5. Évolutions de PHP

À chaque nouvelle version, PHP apporte ces améliorations et nous offre de nouvelles fonctionnalités. Puisque nous en sommes à moderniser, utilisons les dernières évolutions de PHP. En voici trois exemples. Définir des propriétés directement depuis le constructeur d’une classe.

Définir des propriétés en readonly.

Nommer les paramètres à l’appel d’une fonction.

Une fois que tout cela est réalisé, mettons à profit nos outils de bonne tenue de code.

Quand tout est vert, encapsulons ces modifications dans un nouveau commit : Ouvre une nouvelle fenêtrehttps://github.com/vibby/cakephp-bookmarker-tutorial/commit/d971a2f0d10865e008363f2b37249dc12a4b8758

La couche domaine doit aussi évoluer avec ces dépendances, même si nous l’avons limité autant que possible. Mais elle dépend bien entendu de PHP, et peut-être quelques classes natives du langage, comme

.

Point d’étape

Nous voici avec une application bien mieux découpée, contrôlée. Mais le voyage est loin d’être fini ! Dans la prochaine étape, nous verrons comment améliorer la qualité et la confiance dans notre application, et comment brancher une infrastructure plus moderne !

Vincent Beauvivre

Développeur back

Partager

Articles similaires