10) "Avoid Primitives" - Commands
Nous avons encore du travail quant aux entrants de notre système. Celui-ci ne prend en paramètre que des types primitifs avec des signatures de méthodes :
difficiles à faire évoluer
listes de Tuples...
pouvant être cryptiques et donc avec un
fort impact cognitif
public Guid Handle((string nom, int nbGalinettes) terrainDeChasse, List<(string nom, int nbBalles)> chasseurs)
public void Handle(Guid id, string chasseur)
Il existe un moyen d'éviter ce phénomène : les "objets".
En l'occurence nous allons encapsuler ce qui est handlé par nos Use Cases dans des objets de type Command :
Prendre du temps pour comprendre ce qu'est le principe
Avoid Primitive TypesCommencer par refactorer le code du Use Case
DemarrerPartieDeChasse
Si nous avions une couche d'exposition au-dessus, nous devrions mapper les DTOs en entrée vers nos commandes afin de préserver l'encapsulation et pouvoir faire évoluer notre Domain sans impacter les couches supérieures.
DemarrerPartieDeChasse
On peut commencer par transformer les paramètres entrants en
class

Puis on configure l'extraction

Voici l'impact de ce refactoring automatisé sur notre code :
Notre IDE est capable de faire ces changements lui-même
On peut maintenant renommer et bouger cette classe dans notre
DomainDans le namespace
CommandsPour en savoir plus sur le choix de placer les
Commandsdans leDomain, je vous invite à lire ce super article de Vladimir Khorikov
Nous pouvons continuer à travailler sur le
recordafin de lui donner plus de sens métierMalheureusement, à l'heure où j'écris ces lignes mon IDE n'est pas en capacité de faire ce refactoring automatiquement
Nous allons donc adapter par nous-mêmes les appelants en nous laissant guider par les erreurs de compilation
On commence par "fixer" les tests
On adapte notre
Builder
Refactoring du code de production
On se focalise maintenant sur le code de production

On utiliser le Strangler Pattern pour refactorer notre code
On génère un nouvel
overload🔴 5 tests sont maintenant rouges : ils vont nous servir de
driver
🟢 On commence par utiliser l'ancienne méthode dans à partir de la nouvelle pour faire passer les tests
🔵 On peut maintenant "copier / coller" le code de l'ancienne méthode dans la nouvelle
On regarde qui appelait l'ancienne méthode de création

On "plug" les appelants sur la nouvelle méthode
🔵 On peut supprimer de manière totalement
safela méthode "étranglée"
Reproduire ces étapes pour les autres Commands
Après avoir extrait des Command pour chaque Use Case nous pouvons remarquer les patterns suivants :
1
Use Casea pour définition :Command->ResultChaque
Use Caseimplémente la même logiqueCharge la
PartieDeChasseà partir du repositoryVérifie que celle-ci existe
Exécute l'action sur le
DomainSavele nouvel état
1 seule exception à celà : DemarrerPartieDeChasse
Nous allons faire en sorte de factoriser cette logique grâce à notre nouveau typage d'entrée.
Factoriser le code des Use Cases
On commence par traduire le texte ci-dessus sous forme de "contrat".
On crée une nouvelle interface pour nos
Use CasesOn crée également une interface pour contraindre qu'en entrée d'un
Use Caseseule 1Commandpuisse être passée
On l'utilise pour
ConsulterStatusLe gain n'est pas flagrant...
On utilise uniquement les interfaces pour contraindre la méthode
Handle
On continue avec un autre
Use Case:PrendreLapéroOn tombe sur un problème
Le type de retour ici est
void
On crée un type de retour pour ce besoin
On profite d'avoir 2 usages pour essayer de mutualiser du code
On extrait 1 squelette de classe
On extrait également 1
recordcontenant leGuidde la partie
On utilise cette classe dans 1 premier
Use Case
On implémente la méthode
Handle
On "fixe" la classe
ConsulterStatus
Alternativement, nous pourrions utiliser 1 Higher Order Function plutôt qu'une méthode Abstract ici
On refactor les autres
Use CasesEt apporte quelques améliorations
Finalement, voici le code de nos
Use Cases(sans avoir introduit aucune régression)
Confinement des Commands
Nous pouvons ajouter 1 nouvelle règle d'architecture :
Voici le code que l'on peut écrire à l'aide d'ArchUnit :
Nouveau rapport SonarCloud disponible ici.
Reflect
Où avez-vous placé les classes de type
Commanddans votre solution ?Quel impact ce refactoring a eu ?

Last updated
Was this helpful?