Présentation générale

Get Your Hands Dirty on Clean Architecture est la seconde édition d'un livre (initialement sorti en 2019) publié en juillet 2023. L'ouvrage, rédigé dans un anglais agréable à lire, vous est proposé par Tom Homberg (ingénieur chez Atlassian le jour) chez LeanPub et Packt Publishing.

Cette édition, passant de 12 à 15 chapitres (bonifiée de 10 pages) par rapport à l'édition initiale, contient 137 pages de propos hors préface (Avant-propos, remerciements, table des matières) et annexes (index, recommandations d'ouvrages).
Deux dépôts Github (à jour de cette nouvelle édition), dont celui de l'application BuckPal - embryon (sans frontend) de clône du service PayPal mondialement connu - viennent compléter le propos et les extraits de code repris dans l'ouvrage.

Le sous-titre du livre Build 'clean' applications with code examples in Java donne une indication explicite sur le langage / plateforme ciblé.e (version 17) par l'application exemple. A noter qu'un chapitre particulier est quant à lui illustré par du code Kotlin également disponible en ligne. Pour être complet, l'auteur a pris le parti de s'appuyer sur les technologies suivantes pour résoudre les préoccupations techniques sous-jacentes à l'application décrite tout au long du livre : Gradle, Lombok, Spring (Boot), JPA (Spring Data), JUnit 5 / Mockito / AssertJ.

L'ouvrage se lit plutôt dans l'ordre d'apparition des différents chapitres, mais le lecteur aguerri sur le thème couvert pourra aisément naviguer librement entre les chapitres. Le curieux pourra quant à lui se servir d'un index bien fourni pour aller chercher le propos se rapportant à un sujet particulier.

Un livre pragmatique sensibilisant aux architectures Clean / Hexagonal

Sur suggestion par son auteur Tom, j'ai pris connaissance de ce livre dans sa version eBook, et propose ici une revue de son contenu.

En résumé

Tant sur le fond et la forme, nous avons ici affaire à un livre qualitatif se lisant facilement et dont le cheminement s'articule autour d'un mot d'ordre : la maintenabilité du logiciel.

changeability-chenille-papillon

L'approche est pragmatique dans la mesure où l'auteur ne cherche pas à rentrer dans les nuances Clean Architecture / Architecture Hexagonale mais plutôt à valoriser un ADN commun et un style d'architecture centré sur le Domaine métier.
Sans surprise, le propos adopte par moment des éléments de vocabulaire issus du DDD (Domain Driven Design) sans pour autant avoir la prétention de couvrir ce vaste sujet (références ponctuelles vers les Red/Blue Books).
De plus, à l'issue de chaque chapitre, l'auteur valorise le sujet abordé en répondant à la question How does this help me build maintainable software ?

Le propos est également pragmatique car Tom ne cherche pas à évangéliser une organisation du code / un design strict et sensibilise plutôt le lecteur aux avantages et inconvénients des différentes stratégies qui peuvent s'offrir à lui dans l'émergence d'une architecture solide et adaptée aux besoins propres et perspectives court-terme d'un produit donné.

Les exemples de code sont facilement compréhensibles pour les initiés du langage Java (à jour de la version 17) et de la pile technologique retenue. Pour les autres, les explications de texte venant en complément des éléments de réflexions (incarnés par le code) permettront une bonne acquisition des patterns et bonnes pratiques.

Les pratiques de test ne sont pas oubliées, bien que ne prenant pas toute la place qu'elles pourraient mériter dans une approche de conception (pour autant, le livre ne prétendait pas aborder le TDD). Si les illustrations de typologies de tests sont satisfaisantes, le recours à Mockito (qui reste la librairie la plus employée pour la création d'objets doubles de test) pourrait être comparé à, voire remplacé par, de simples doubles créés de toute pièce, ceci d'autant plus que l'auteur évoque cette alternative. Mention spéciale pour l'illustration d'ADR (Architecture Decision Record) et surtout son automatisation pour valider en continu le respect de règles d'architecture (et en prime la mise à disposition d'un exemple de DSL spécifique au contrôle d'architecture hexagonale).

Selon moi, l'objectif d'illustration de Clean / Hexagonal Architectures par la pratique est atteint, et je pense qu'il y a matière à renforcer / ajouter certains chapitres pour apporter encore davantage de valeur tout en suivant la ligne de conduite déjà bien affirmée.
Bien que n'ayant pas lu la 1ère édition, je trouve les chapitres ajoutés pertinents même si le chapitre A Component-Based Approach to Software Architecture s'écarte un peu du thème principal du livre et pourra plutôt être vu comme une ouverture / rappel que le respect des principes SOLID et l'usage de patrons de conception est primordial au sein d'un cercle (pour ne pas dire "couche") de l'architecture.

En conclusion

De par sa taille c'est un livre qui se lit assez facilement et rapidement. Personnellement, j'ai joué le jeu de le lire de façon linéaire et j'ai trouvé une bonne cohérence des enchaînements.
Les exemples présent dans l'ouvrage (avec du code simplifié pour des besoins pédagogiques) illustrent bien les intentions partagées, et le code mis à disposition sur GitHub permet de voir un exemple complet incarnant le style d'architecture mis en avant.

Compte-tenu de sa taille et sur quelques éléments de comparaison avec d'autres ouvrages, autant je trouve le prix correct pour l'ebook selon le canal d'achat (Abonnement Packt / Amazon FR / LeanPub / Packt), autant j'ai un peu plus de mal à défendre le prix de la version papier distribuée par Amazon (la formule eBook + paperback sur Packt s'avérant plus intéressante).

Dans tous les cas, c'est un ouvrage que je peux recommander chaudement à tout développeur Java initié ou non à l'architecture hexagonale et curieux d'en comprendre les enjeux et forces. C'est également un bon point de départ pour ceux qui souhaitent s'initier au DDD (un peu la partie stratégique, davantage la partie tactique).

Revue détaillée

Chapitre 1 : Maintainability

Tom insiste sur l'importance de la maintenabilité parmi les différents indicateurs qualimétrique qu'il est possible de recenser (le lecteur pourra s'intéresser à la norme ISO 5055 publiée en 2021 et vulgarisée par Sylvain Cailliau, Directeur Technique chez CAST Software). Il défend le point de vue que la maintenabilité a une incidence sur plusieurs indicateurs qualimétriques grâce à sa composante "variabilité" (facilité à changer, en anglais "changeability"), concluant que le coût du changement (i.e. ajout d'une fonctionnalité), et l'évolution de ce coût dans le temps, était fortement corrélé à la maintenabilité.

C'est un propos qui trouve d'autant plus résonance quand le marché de l'offre / demande est tendu : l'auteur défend l'idée que la rétention des développeurs puisse être corrélée à la maintenabilité (et aux efforts consentis en ce sens) des logiciels sur lesquels ils interviennent.

C'est donc autour de cet enjeu que la suite de l'ouvrage va s'articuler, l'auteur tentant de répondre à l'issue de chaque chapitre à la question "How does this help me build maintainable software?"

Chapitre 2 : What’s Wrong with Layers?

Tom, après avoir analysé le mode de fonctionnement d'une équipe réalisant un logiciel sur base d'architecture n-tiers, dénonce sa vulnérabilité au changement ... et donc le risque accru sur la maintenabilité du logiciel.

En particulier, il pointe du doigt un défaut majeur : aborder la conception en partant de la base de données, laquelle va imposer les fondations de l'architecture, exposant notamment le code métier à être couplé au code de persistence.

L'auteur ne manque pas de mettre en avant les difficultés à tester unitairement le code dans ce type d'architecture (non pas que ce soit impossible, mais que cela nécessite un effort important et aboutisse à un code de test difficilement lisible et maintenable).

Pour finir, il souligne les limitations de ce type d'architecture (notamment lorsque le couplage est fort) dans l'opportunité de paralléliser des développements.

Chapitre 3 : Inverting Dependencies

Plutôt que de faire l'apologie de la Clean / Hexagonal Architecture, l'auteur introduit ces styles d'architecture en s'appuyant sur les principes SOLID et en particulier le SRP (S, Single Reason to Change Principle ou plus connu sous le nom Single Responsibility Principle) et le DIP (D, Dependency Injection Principle).

Il introduit par la suite la Clean Architecture, puis l'Architecture Hexagonale (qu'il décrit comme étant plus concrète que l'abstraction donnée par la Clean Architecture) et sa terminologie, ceci sans les opposer. Le code illustrant le livre et mis à disposition sur Github se conforme à l'Architecture Hexagonale en reprenant notamment sa terminologie dans la structure et le nommage des classes.

Que ce soit pour la Clean Architecture ou l'Architecture Hexagonale, Tom met en avant un terrain propice à la mise en oeuvre des concepts DDD (Domain Driven Design) dont certains termes sont d'ailleurs empruntés par ces architectures.

Chapitre 4 : Organizing Code

Dans ce chapitre, l'auteur partage une réflexion itérative qui finit par aboutir à une proposition d'organisation du code reflétant l'architecture retenue.

Il insiste sur l'importance et la nécessité que la structure de code incarne l'architecture, notamment pour permettre au développeur de trouver rapidement ses repères. Pour autant, il souligne le poids de la structuration dans la prévention dans le temps du risque de déviation et de désorganisation du code / violation des principes énoncés par l'architecture.

Chapitre 5 : Implementing a Use Case

Premier chapitre matérialisant des extraits de code en guise d'illustration du propos, on peut relever l'utilisation des annotations Lombok (en l'occurence @RequiredArgsConstructor) sans présence d'explications pour les non initiés (une explication sera donnée au chapitre 10).

Pour des raisons évidentes d'encombrement, les imports sont masqués, ce qui fait que le recours à l'annoation @NotNull de Bean Validation - sans expliciter l'import - nécessite un aller / retour avec le dépôt GitHub pour lever tout doute possible et expliciter le package. Pour autant, on appréciera de constater que l'API Bean Validation à jour du move Java EE vers Jakarta EE (pour sa version 9).

Si les simplifications sont parfaitement justifiées pour des raisons de lisibilité, des liens vers le code source équivalent (en ligne sur GitHub) pourraient être pertinents pour que le lecteur puisse visualiser la classe dans son ensemble (imports, constructeur, champs, etc.).

En matière de logique métier, le lecteur appréciera les explications d'une stratégie de répartition des contrôles de validations (données / use case). On regrettera par contre l'utilisation du même nom de classe Validator que la classe homologue issue de l'API Bean Validation.

Au rayon des bonnes pratiques attribuables à DDD, Tom utilise bien un wrapper pour l'id d'un compte (cf. constructeur(Long l)) mais omet de sensibiliser au smell Primitive Obsession.
A l'inverse, l'exemple proposé page 41 expose un numéro de sécurité sociale sous forme de chaîne de caractères, et les explications autour du Value Object ne vont pas tacler le problème de validation / validité et typage de cette donnée. Le message véhiculé porte avant tout sur le regroupement de données dans une data class et le typage de certaines données (City, ZipCode) représentées sous forme de chaînes de caractères, typage permettant d'éviter les confusions lors de l'appel au constructeur. L'intérêt d'utiliser des types dédiés plutôt qu'un primitif n'est ni argumenté ni illustré sous l'angle de la validation.

D'un point de vue architecture, il aurait pu être intéressant d'aller légèrement plus loin que de mentionner CQS (Command Query Separation) / CQRS (Command and Query Responsibility Segregation) en illustrant le découplage et la possibilité de scaler l'une des briques indifféremment de l'autre.

Chapitre 6 : Implementing a web adapter

Dans ce chapitre, il est intéressant de noter certains conseils, et notamment la pertinence du découpage de contrôleur (dont favorisation travail en parallèle et limitation conflits) et du choix du nommage (anti CRUD "usual suspects").

On regrettera que la gestion des droits (assimilée à l'adapteur de gauche) ne soit pas illustrée ni dans l'ouvrage, ni dans le code mise à disposition.

Chapitre 7 : Implementing a persistence adapter

Tom nous livre un excellent argumentaire (testabilité à l'appui) pour inciter la séparation des préoccupations (à minima) au niveau des ports (concept DDD).

On notera en passant le recours à des fonctionnalités du langage Java pleinement intégrées au JDK 17 (utilisé par le code mis à disposition), notamment les triple double quotes (""" texte pouvant être mis sur plusieurs lignes """) ainsi que les classes records.

Là encore, sur l'illustration page 61 de la classe Account, l'absence de représentation du constructeur (l'annotation Lombok étant étonnamment non représentée cette fois dans l'extrait) prive le lecteur de la description complète d'une stratégie / patron Factory Methods (le code source en ligne clarifiant la stratégie avec la présence - génération par Lombok - d'un constructeur privé).

Point de vigilance important, même si dans le cas illustré (classe Account avec pour seul champs son id de type Long) cela ne pose pas problème, l'usage sur une entité JPA de Lombok et de son annotation @Data est controversé car source de dysfonctionnements.

Chapitre 8 : Testing Architecture Elements

Je commencerai par quelques axes d'améliorations, à savoir (page 74) que l'annotation @MockBean (rattachée à Spring Boot) n'est pas décrite par l'auteur (origine, principe, etc.). Sur cette même page, le recours aux constantes issues de org.springframework.http.HttpHeaders et org.springframework.http.MediaType pourrait valoriser les bonnes pratiques concernant les magic strings (Clean Code).
Enfin, le lecteur pourrait s'attendre (pages 76 et 79) à une explication des subtilités de @DataJpaTest (ainsi que @SpringBootTest), laquelle ne ferait pas de mal pour les non aguerris sur Spring (en particulier concernant la gestion de transactions sur l'exécution de tests).

La mention, page 77, de TestContainer, et sa justification, sont appréciables. Cependant, le recours à l'API Spring Data pour vérifier l'état final en base pourrait être discuté. En effet, on pourrait vouloir valider l'état en base avec une requête SQL et non un composant déjà utilisé par l'adapteur de droite.
Une remarque en ce sens (ex. utilisation de JdbcTemplate ou JdbcClient pour les plus à jour de Spring 6.1 / Spring Boot 3.2) dans le même esprit que la mention TestContainer ne ferait pas de mal pour souligner les bonnes pratiques sans pour autant complexifier l'exemple (quitte à proposer l'évolution sur le dépôt GitHub uniquement).

Pour ce qui est des tests unitaires des cas d'utilisation, le style adopté est le London school (mockist) et Tom recours intensivement à Mockito (et son API orientée BDD) pour spécifier les comportements et vérifier la cohérence des appels. Cette approche peut faire sens dans une démarche de conception en TDD outside-in, mais reste discutable dans une logique de test after.
Bien que l'auteur sensibilise aux risques sur l'excès d'utilisation de Mockito (en indiquant par exemple que l'effort pour créer de vraies instance du domaine n'est pas plus important) et évoque la terminologie des différentes formes de doubles, il ne l'illustre ni dans ses exemples, ni dans le code mis à disposition. C'est certainement un axe d'amélioration (quitte à proposer les 2 versions dans le code en ligne).

Chapitre 9 : Mapping between Boundaries

Outre l'illustration et étude (avantages / inconvénients) de différentes stratégies de mapping entre couches, le mot d'ordre de l'auteur est que non seulement une stratégie de mapping peut être amenée à être remise en question / changer au fil du temps, mais qu'il peut être judicieux d'adapter la stratégie de mapping au cas par cas (à l'échelle d'un use case).

J'ai personnellement trouvé pertinent et judicieux la stratégie de mapping dite "one-way" qui peut permettre de limiter l'effort de transformation d'une représentation à une autre.

Chapitre 10 : Assembling the application

Dans ce chapitre, on pourra relever le pragmatisme quant à la volonté d'avoir un domain métier agnostique de tout framework technique. En effet, tout en rappelant l'enjeux, Tom propose une solution élégante par l'intermédiaire de la redéfinition d'annotations Spring (lesquelles viendront méta annoter des annotations métier). Le couplage devient donc centralisé dans ces annotations spécifiques, et l'effort d'adaptation en cas de changement de framework maîtrisé.

Soucieux à travers les différents chapitres (à commencer par celui dédié à la structure du code) de la visibilité nécessaire et suffisante pour chaque composant (comprendre classe), l'auteur s'intéresse ici à l'injection de dépendances. C'est ainsi qu'il décrit différents styles proposés par Spring Framework avec leurs avantages et défauts. En particulier Tom justifie ici de la présence d'une classe de configuration dans le même package que les composants (en package private) qu'elle va instancier, rendant de fait impossible l'utilisation directe des composants (sauf à se placer dans le package en question).

Logique d'assemblage mise à part, le contenu de ce chapitre est fortement coloré par le framework de DIP (Dependency Injection Principle) utilisé dans l'architecture Java proposée.

Chapitre 11 : Taking Shortcuts Consciously

Après avoir introduit le chapitre par la métaphore de la vitre brisée (Broken Window Theory), l'auteur évoque les ADRs (Architecture Decision Records) comme une documentation visant à réduire l'effet de la vitre brisée.

Il passe par la suite en revue les différents compromis qu'il est possible d'envisager (ainsi que leurs limites) par rapport à une approche state of the art, tout en insistant sur la nécessité de documentation.

Chapitre 12 : Enforcing Architecture Boundaries

Suite logique au style de documentation introduit dans le chapitre précédent, ce chapitre vient compléter par des exemples concrets (et une proposition de DSL ArchUnit pour architecture hexagonale) la documentation / vérification automatisée de règles d'architecture.

Au préalable, Tom s'intéresse aux capacités fournies par le langage Java pour limiter / contraindre la visibilité des composants, jouant sur les 4 modificateurs mis à disposition par le langage depuis sa genèse. Une approche préventive intéressante qui va venir en complément du filet de sécurité constitué par des tests automatisés recourant à la librairie ArchUnit.

Si on se plonge dans l'univers Java, une voie alternative qu'aurait pu explorer l'auteur serait celle des Java Modules introduits depuis le JDK 10. En effet, sa mention, réflexion, voire description d'une stratégie (source d'inspiration) reposant sur JPMS (Java Platform Module System) constituerait une illustration concrète d'une fonctionnalité jusque là à priori peu utilisée par les développeurs Java.

Chapitre 13 : Managing Multiple Bounded Contexts

Nouveau chapitre de cette seconde édition, l'objectif est de répondre à la demande de plusieurs lecteurs en ce qui concerne le passage à l'échelle (par l'exemple) d'une architecture hexagonale avec plusieurs contextes bornés (terminologie DDD).
Tom y donne des pistes et références sans chercher à décortiquer les patterns tactiques de DDD (préférant pointer les ouvrages de référence) et les s'appuie sur des illustrations sous forme de réprésentation de type diagramme de composants. J'aurais aimé avoir également du code pour illustration, et à défaut d'en avoir dans le chapitre il aurait été judicieux d'inclure un second contexte borné dans le code BuckPal mis à disposition (contrairement au livre dont l'édition ne peut subir de mise à jour mineure que pour les lecteurs LeanPub), c'est une suggestion que l'auteur pourra mettre en oeuvre sur le dépôt GitHub s'il le juge opportun (pourquoi pas aidé d'une contribution sous la forme d'unePull Request ...).

Autre concept issu de DDD, les Domain Events sont évoqués comme une solution pour transmettre sans couplage fort des modifications d'un aggrégat à d'autres aggrégats non inclus dans le même contexte borné. Malheureusement ni le chapitre, ni le code mis à disposition, ne proposent une illustration de cette stratégie.

Chapitre 14 : A Component-Based Approach to Software Architecture

Dans cet avant-dernier chapitre, Tom nous soumet une réflexion intéressante, codebase à l'appui, même si Kotlin risque d'être un langage non maîtrisé par la plupart des lecteurs. D'ailleurs, s'il indiquait en préambule qu'il expliquerait les subtilités du langage, code à l'appui, dans la mesure où il ne reprend pas d'extraits de code dans le livre cela a pour conséquence de ne pas non plus commenter / expliquer ce dernier).
Pour autant, l'objectif porte plutôt sur l'organisation du code plus que les détails d'implémentation en Kotlin. Le chapitre est pertinent et je l'ai plutôt perçu comme une ouverture (un rappel) aux bonnes pratiques de conception (dont SOLID), non pas d'une architecture donnée, mais au sein d'une couche donnée de l'architecture.

Chapitre 15 : Deciding on an Architecture Style

Fidèle à lui-même, l'auteur ne donne pas de formule magique, expliquant qu'il n'y a pas de silver bullet, préconisant plutôt un approche méthodologique (start simple, inspect and adapt).
Pour autant, une synthèse ou liste de styles d'architectures aurait pu apporter des éléments concrets au lecteur.

Recommandations d'ouvrages

Il est intéressant de constater que l'éditeur recommande Designing Hexagonal Architecture with Java en fin d'ouvrage (la réciproque est vraie également), lequel (certes publié début 2022, mais il semblerait qu'une seconde édition soit en cours d'écriture) pourrait s'avérer assez proche du présent livre mais avec une orientation technologique vers Quarkus (ouvrage proposant 460 pages contre 168 ici, mais cela vient certainement du scope qui aborde Quarkus et le développement Cloud Native, section correspondant à un tiers des chapitres).

Axes d'améliorations sur l'ordonnancement des chapitres

Comme je l'ai déjà exprimé, Get Your Hands Dirty on Clean Architecture se lit facilement et l'ordre des chapitres est cohérent, à condition d'avoir quelques notions autour de Spring (Boot) sans quoi la lecture pourrait être interrompue par quelques recherches à mener pour mieux appréhender le code technique.
Il pourrait être intéressant de rendre les premiers chapitres davantage agnostiques du framework, ne conservant que les tests unitaires et reléguant les autres tests mettant en oeuvre la fabrique / l'infrastructure à une section dédiée Spring (Boot) en dernière partie d'ouvrage. Cela permettrait à la fois d'être plus légitime pour introduire les concepts spécifiques au framework, mais aussi potentiellement d'aller plus loin dans la couverture par rapport à l'offre de valeur du framework.