Challenger l’architecture d’un projet en tant que développeur junior (Partie 2)

Cet article fait suite à un premier dans lequel nous avons appliqué certains concepts de Clean Code au niveau de la structure des méthodes et des classes. Mais peut-on élargir tous ces concepts au niveau de l’ensemble d’un projet ? Est-ce possible de les appliquer sur un projet existant ? C’est possible et c’est là qu’interviennent les systèmes d’architecture. On ne pourra pas aborder l’ensemble du sujet dans cet article. On va donc se concentrer sur la Clean Architecture. Avant tout, je souhaiterais expliquer pour quelles raisons j’ai choisi de vous parler de cette architecture.

En tant que développeur, on a pu entendre parler de près ou de loin d’architecture en couches, MVC et bien d’autres. Elles permettent un premier découpage afin de regrouper les parties du code qui ont des préoccupations communes mais dont leurs dépendances convergent toutes vers le modèle de données.

Ce dernier peut contenir des objets qui vont représenter l’état et la logique métier de votre application. Parfois, ces objets ne contiennent pas de logique métier : il s’agit alors d’un modèle anémique. Sur des projets avec une complexité métier réduite cela ne pose pas de souci. Cependant, à mesure que le nombre d’interactions entre les différentes couches grandira, il va falloir :

  • ajouter des comportements dans une couche modèle qui ne la concerne pas (des actions agissant sur la vue en fonction des données par exemple) ;
  • ou déplacer des comportements métier vers les couches supérieures. Cela peut conduire à des services très denses ;
  • et enfin, une difficulté accrue pour tester en isolation les comportements de votre application.

C’est ici que la Clean Architecture sort son épingle du jeu en concentrant les dépendances vers le domaine (ce qui est différent du modèle de données). Il s’agit d’un ensemble d’objets interconnectés qui contiennent la logique et les règles métier de votre application. L’avantage, c’est que vous contrôlez entièrement le domaine. De ce fait, vous allez pouvoir faire évoluer votre application sans dépendre de sources extérieures.

Cette architecture est adaptée pour les projets qui durent plusieurs mois/années. Pour de grands projets, des modules de haut niveau orientés métier et eux-mêmes construits avec cette architecture sont plus appropriés. Il est très possible d’avoir des parties de votre application en Clean Architecture et d’autres pas. Je me réfère au cadre d’un projet existant par exemple.

Le but derrière tout ça est de pouvoir garder un minimum de qualité afin de pouvoir continuer à faire évoluer votre application sans trop de souci. Une métaphore que j’ai aimé dans cet article résume bien cette idée : “...c'est comme nettoyer les surfaces de travail et les équipements dans la cuisine. Il est impossible de ne pas salir les choses lorsque l'on cuisine, mais si l'on ne nettoie pas rapidement, la crasse sèche est plus difficile à enlever et toutes ces saletés empêchent de préparer le plat suivant.”

Utilisée depuis très longtemps dans différentes formes mais démocratisée depuis quelques années, cette famille d’architecture est composée de plusieurs variantes comme la célèbre architecture hexagonale. Elle illustre bien l’utilisation de tous les concepts utilisés et cités précédemment à un niveau macroscopique.

Schéma de la Clean architecture

Ce schéma concentre l’ensemble des idées des différentes déclinaisons. Pour illustrer son fonctionnement, nous allons dans la suite de cet article utiliser l’architecture hexagonale. La nomenclature diffère un peu mais le principe reste le même.

Objectif

La principale préoccupation de la Clean Architecture est de séparer les différentes responsabilités d’une application en plusieurs couches. Mais la règle primordiale qui permet à cette architecture de fonctionner est la règle des dépendances. Cette règle stipule que les dépendances du code source ne peuvent pointer que vers l'intérieur. Commençons par la couche centrale, le domaine, puis remontons peu à peu les différentes couches.

Domaine

Les déclinaisons de la Clean Architecture ont toutes un noyau central correspondant au domaine (la couche Entities). Il s’agit de la couche qui représente la valeur métier de votre application.

C’est dans cette zone que vous allez manipuler différents agrégats, entités et value objects qui contiendront les règles de gestion de votre métier. Si vous souhaitez écrire de la logique métier en dehors de vos entités, vous pouvez utiliser des services de domaine.

Par exemple, imaginons que vous souhaitez gérer la validité d'un ensemble de réservations suivant si les éléments associés existent dans un service tiers (base de données etc.). Cette opération métier a besoin d'accéder à un référentiel ou une passerelle afin de récupérer des données utiles à cette validation. Il n'est pas souhaitable que votre entité Reservation s'occupe de cette responsabilité car cela dérogerai au principe de responsabilité unique de SOLID. Alors, il serait plus judicieux d'extraire cette logique dans un service de domaine dédié à la validation de ces réservations.

Il faut faire attention à utiliser les services de domaine avec parcimonie car cela peut conduire à des entités anémiques (toute la logique est centralisée dans les services de domaine).

Enfin, le domaine ne doit dépendre d’aucun élément des couches extérieures.

Exemple simplifié d’un domaine pour gérer des produits et des paniers

Exemple d'un domaine pour gérer des produits et des paniers

Mais comment le domaine peut-il communiquer avec l’extérieur si on ne peut pas y ajouter des éléments extérieurs ? Pour ce faire, il existe des ports définis à l’aide d’interface.

Il existe deux types de port :

  • Primaire (ou Input) ⇒ pour accéder au domaine depuis l’extérieur, il peut contenir les interfaces de vos cas d’utilisation ;
  • Secondaire (ou Output) ⇒ pour que le domaine accède aux services extérieurs sans en dépendre, il peut contenir les interfaces de vos repositories par exemple.

Ces ports seront implémentés par des adaptateurs dans différentes couches selon que ces ports sont primaires ou secondaires.

Application

La couche application (Use Cases) permet de définir des facades à l'aide de services afin d'exposer vos différents cas d’utilisation. C'est ici que les transactions, interactions et l’ensemble des données en provenance et à destination de la couche domaine sont orchestrés.

Aussi, c'est à cet endroit que vous pouvez utiliser les interfaces de repository (pas les implémentations) nécessaires à l'exécution de la logique du domaine.

Cette couche ne contient pas de logique métier car elle est déjà présente dans votre domaine. Il peut s'agir de vos entités ou vos services de domaine. Ces derniers seront beaucoup plus unitaires que des services d'application.

Autre point important, il ne s’agit pas du point d'entrée de votre application mais simplement de l’ensemble des fonctionnalités répondant à votre besoin métier. Il existe une autre couche dédiée pour exposer votre application et pour qu’elle communique avec l’extérieur. Il s’agit de la couche infrastructure.

Infrastructure

Cette couche correspond aux couches Interfaces Adapters and Frameworks and Drivers. Elle peut se décomposer de plusieurs manières au niveau de l'arborescence de vos fichiers mais l’idée derrière est similaire : c’est ici que vous allez implémenter le code concernant la base de données, l’authentification ou tout autre service extérieur ainsi que le code pour exposer votre système.

C’est la couche la plus éloignée du domaine car on ne veut pas s’imprégner des contraintes des systèmes extérieurs.

On peut découper cette infrastructure en deux parties :

  • Primairepour accéder au système depuis l’extérieur, il peut contenir les contrôleurs qui exposent votre API et les mappers pour les données à exposer ;
  • Secondairepour que votre système accède aux services extérieurs, il peut contenir les adaptateurs (implémentations des ports secondaires du domaine) pour une base de données, un système d’authentification, un système de paiement ou toutes autres API extérieures).

Schéma d’une architecture hexagonale (Ports et Adaptateurs)

Les contextes délimités (bounded contexts)

Il est recommandé de découper votre projet de manière verticale. Plutôt que d’avoir un découpage hexagonal au niveau global de votre application, vous pouvez d’abord avoir un découpage par contexte métier.

Il s’agit d’une technique empruntée au DDD (Domain Driven Design) afin de gérer des modèles métiers complexes. En plus de centrer les développements autour du domaine, le DDD apporte différentes stratégies pour imaginer différentes représentations d’un concept et définir des manières de communiquer entre les modèles métiers.

On peut l’illustrer avec notre schéma ci-dessus : un contexte pour le panier (Cart) et un second pour le catalogue (Catalog). Ensuite, dans chacun de ces contextes, avoir un découpage hexagonal.

Cela permet :

  • d’éviter de transporter des informations qui ne sont pas en lien avec votre contexte ;
  • de pouvoir changer chirurgicalement des parties de l’infrastructure (utiliser des bases de données différentes pour votre panier et votre catalogue par exemple) ;
  • d’éviter d’altérer les comportements attendus entre différents contextes métier de votre application ;
  • d’avoir un domaine encore plus proche du besoin métier en termes de langage parlé car vous pouvez représenter un concept sous différentes formes (Product pour Catalog et Article pour Cart par exemple). Il s’agit du langage omniprésent (Ubiquitous Language) ;
  • et finalement d’obtenir une base pour un découpage de votre application en microservices si besoin, et si vous avez les ressources nécessaires.

Conclusion

En premier lieu, pour challenger le code d’une fonction, d’une classe ou d’une merge request, vous pouvez vous baser sur un ensemble de principes de conception.

D’une part, cela apporte du professionnalisme dans votre travail car vos arguments seront basés sur des principes éprouvés par des développeurs expérimentés. En parallèle, cela aide à diminuer le syndrome de l’imposteur en tant que développeur junior car vous apportez des arguments légitimes face à d’autres personnes potentiellement plus expérimentées.

Par ailleurs, il est primordial de s’approprier les principes plutôt que de les appliquer de manière dogmatique. Sinon, cela peut conduire à des designs compliqués qui ne correspondent pas du tout au besoin. Cela peut aussi vous empêcher d’être à l’écoute de nouvelles idées.

De ce que j’ai pu rencontrer et lire à propos de la conception d’une application, il est souvent utile de voir si vous ne cassez pas un des principes SOLID lorsque vous appliquez un des autres principes de conception. Je pense en particulier à DRY ou KISS car l’application de ces principes peut impliquer des dépendances. Ayez en tête qu’il y a toujours une contrepartie quand vous appliquez un principe.

Enfin, les systèmes d’architecture permettent de regrouper et utiliser l’ensemble de ces principes de conception sur l’entièreté d’un projet. La Clean Architecture est un parfait exemple qui implique beaucoup de ces principes.

Même si cette architecture ne se prête pas à tous les contextes, il convient souvent à beaucoup de projets. Le découpage en couche que ce soit en 3, 4 ou plus et la règle des dépendances permettent de tester et changer des parties de votre application sans impacter l’ensemble. C’est un gain de coût sur le moyen/long terme.

Ce qu’il faut retenir en priorité, c’est que la couche domaine est plus importante que l’architecture en elle-même car elle représente votre valeur ajoutée alors que les autres couches ne font pas partie intrinsèquement de votre métier.

Pour construire la couche domaine, vous pouvez par exemple commencer par coder en vous essayant au TDD et y associer des principes de conception. Vous pouvez les appliquer lors des phases de refactoring. Ça va permettre de construire un design au plus prêt du besoin de votre client.

Même en tant que développeur junior, il est possible d'apporter de la valeur au code. Il ne faut pas vouloir tout révolutionner d'un coup mais mettre en place des bonnes pratiques au fur et à mesure.

Sources