Notre implémentation de la Clean Architecture — Zoom sur les Use Cases

Nicolas Poste
Ceetiz
Published in
7 min readSep 21, 2021

--

Chez Ceetiz, nous sommes très attachés aux bonnes pratiques de développement, à l’efficacité, mais également au partage !

💭 C’est d’ailleurs pour ça que nous partageons cet article avec vous… 😊

(Loverna Journey, unsplashed)

Le choix d’une architecture pérenne et pertinente est indispensable. J’ai d’ailleurs écrit tout un article sur le sujet, du même nom : Optez pour une architecture pertinente et pérenne. J’y présente quelques architectures qui seront d’ailleurs mentionnées dans cet article, alors n’hésitez pas à y faire un tour !

Il n’y a pas de solution universelle en ce qui concerne le choix d’une architecture mentionnée en particulier (CQRS, Clean Architecture, Event Sourcing, Microservices, …), tant qu’elle répond au besoin. Il est dans certains cas possible d’en utiliser plusieurs.

Dans cet article, nous vous partageons nos différentes réflexions et approches sur l’implémentation de la Clean Architecture qui répond à nos besoins.

Pourquoi la Clean Architecture ?

Pour répondre à cette question, commençons par détailler notre coeur de domaine : la vente e-commerce d’activités touristiques. Nous sommes dans le cas d’un domaine fonctionnel riche et complexe.

💡 Notons également que la Clean Architecture est un des moyens architecturaux, parmi d’autres, pour appliquer les concepts du DDD, Domain Driven Design. DDD est donc de plus haut niveau, la Clean Architecture étant plus technique.

Nous avons identifié plusieurs domaines fonctionnels (Recherche d’activités, Demande de devis, Confirmation de commande, Gestion du compte client, …). Partagé et étudié avec le métier, ce découpage aide notamment à faciliter les échanges entre collègues : nous parlons le même langage (on parle dans ce cas d’Ubiquitous Language).

La volumétrie n’est pour nous pas tellement un problème, nous n’avons par conséquent pas spécialement besoin d’exploiter une architecture CQRS. Exit donc cette solution qui, malgré ses indiscutables avantages, peut être un peu plus lourde à mettre en place (et encore, ça se discute). Nous gardons néanmoins le pattern CQS, pour la séparation de la partie Command de la partie Query.

N.B. : nous avons néanmoins quelques commandes, qui sont traitées en asynchrone. Nous adaptons la solution technique au besoin fonctionnel.

Nous n’avons pas non plus besoin d’Event Sourcing, en tout cas pour le moment.

Dans notre cas, le choix le plus pertinent est celui d’exploiter la Clean Architecture dans l’intégralité de notre SI. Si elle est bien implémentée, cette architecture nous permettra d’aller sur une architecture CQRS ou Event Sourcing au besoin, sans changer le code du domaine. Sauf rares exceptions justifiées, nous limitons au maximum les CRUDs qui ne contiennent que très peu de valeur métier.

Nous avons également choisi de partir sur une architecture en microservices, pour bien compartimenter nos domaines fonctionnels. En fonction de la volumétrie, nous pourrons ainsi allouer plus ou moins de CPU, mémoire, instances d’applications, … à l’un ou l’autre des domaines. Chaque application ne repose que sur un seul domaine. Un domaine peut quant à lui disposer de plusieurs applications, même si au moment où nous écrivons ces lignes, il n’y a qu’une application par domaine. Nous pourrions éventuellement avoir plusieurs applications dans le même domaine si nous identifions des sous-domaines.

Dans notre métier nous disposons de plusieurs contextes, tels que B2C, B2B, B2B2C, … Ces différents contextes peuvent traverser plusieurs domaines, il n’y a pas spécialement de raison qu’une commande B2C soit complètement différente d’une commande B2B, qu’une recherche d’activité B2B diffère d’une recherche d’activité B2C (éventuels filtres liés aux droits exclus).

Les Use Cases

Un Use Case est un moyen de découvrir une entité, un agrégat, une interface ou une fonctionnalité et leur workflow associé.

En plus de permettre de faciliter la communication entre la technique et le métier, ils mettent l’accent sur des fonctionnalités importantes de notre SI, des concepts clé. Ils émergent habituellement lors de conversations entre les différents acteurs du produit.

Leur documentation est essentielle et doit être soignée.

Dans le découpage en couches de la Clean Architecture ou DDD (concepts très proches), les Use Cases se situent dans la couche nommée “Application Layer”.

Source : khalilstemmler.com

En faisant le lien avec le livre DDD d’Eric Evans, on parle de Application Services.

Les choix pris à Ceetiz

Nos choix en ce qui concerne le découpage technique

Comme mentionné dans notre précédent article, Une documentation vivante et utile, nous sommes sur un mono repository et avons un module Maven par application. Nous avons un parti pris qui est de séparer clairement les couches de domaine, infrastructure et application, sous forme de sous-modules Maven :

  • la couche domaine est agnostique de tout framework et contient le modèle (non anémique !) ainsi que les uses cases. Les dépendances utilisées sont très limitées : librairies de logs et commons-lang3
  • la couche infrastructure contient les adaptateurs de droite
  • la couche application contient les adaptateurs de gauche et la glue (IoC)

Chaque application est structurée de la même façon.

🧠 Pour rappel, chacune de nos applications est à l’intérieur d’un seul domaine.

Différences avec la Clean Architecture “officielle”

Nous avons opté pour un nommage et des approches qui diffèrent quelque peu des préconisations du livre Clean Architecture, mais nous pouvons les expliquer.

Dans notre découpage en modules Maven, nous n’avons que trois couches plutôt que quatre. Par rapport à ce schéma, les couches Application Layer et Domain Layer ne font qu’un. Nous isolons toutefois, à l’aide de packages, les Use Cases du reste des entités et agrégats, ce qui constitue d’une certaine façon une couche distincte.

La couche “adapters” est nommée infrastructure dans notre repository, car elle contient les dépendances vers les solutions techniques : MongoDB, Recaptcha, client mail, … Son rôle reste toutefois sensiblement le même que celui décrit dans les livres.

La couche “infra” est nommée application dans notre repository, car elle contient les dépendances vers Spring Boot et ce qui en fait une application à part entière : IoC, fichiers de propriétés, classe main (boot), adapters de gauche (REST Controllers), …

Nous restons ouverts et pourrons re-challenger ces choix de nommage. Ils ne sont pas gravés dans le marbre !

Notre vision des Use Cases

Les Use Cases font partie du domaine, ils font office de porte d’entrée à nos différentes fonctionnalités. Par conséquent :

  • un contrôleur REST n’appelle aucune méthode d’une entité directement, il doit passer par un Use Case
  • les Use Cases ne manipulent que des objets du domaine (les transformations depuis des DTO se fait en amont, et vers des DTO se fait en aval)
  • les Use Cases servent d’ordonnanceur
  • ils ont une définition fonctionnelle forte et bien définie

En revanche, il est primordial de garder les entités riches : les entités ne sont pas anémiques, la programmation orientée objet garde tout son intérêt.

Les Use Cases, instanciés qu’une seule fois au lancement de l’application, pourraient être confondus avec ce qui est souvent appelé Service (et annoté @Service avec Spring), mais nous avons préféré les rendre agnostiques de tout framework. Les modules Maven “domain” ne contiennent en effet que très peu de dépendances comme mentionné précédemment.

La mise en place des Use Cases

Nous nommons les Use Cases par la description de ce qu’ils font. Une classe correspond à un Use Case. Exemple : CheckIfActivityDateIsPossible. Notez qu’ils commencent tous par un verbe.

Dès leur mise en place, nous avons annoté nos Use Cases avec une annotation créée à l’occasion : ‘@UseCase’. Cela nous a rapidement permis d’en extraire une documentation statique (avec classgraph, cf notre précédent article).

Dans un second temps, nous l’avons couplée avec le scanneur “class dependency map” de classgraph, ce qui nous a permis d’obtenir des graphes de dépendances entre les Use Cases.

Arbre de dépendances des appels pour confirmer une commande (B2B ou B2C).

👍 Cette documentation, statique, est mise à jour à chaque push sur la branche principale et reflète par conséquent l’état actuel du SI.

L’AOP à la rescousse pour offrir davantage de fonctionnalités

Désormais, nous exploitons cette annotation également avec l’AOP pour décorer les appels des méthodes publiques.

Ceci se fait très simplement :

Ces informations sont transmises à notre ELK et nous sont restituées sous forme de graphiques divers et variés, répondant à nos besoins.

Temps moyen d’exécution de différents Use Cases

Ainsi, nous pouvons :

  • obtenir le temps d’exécution de chaque Use Case
  • surveiller le taux d’échec
  • compter le nombre d’appels
  • faire diverses statistiques et prévisions

Bénéfices d’une telle implémentation

Les Use Cases ainsi implémentés, nous mettons en évidence les points d’entrée de notre système et donc les commandes, tout en les monitorant efficacement. Ce choix est entièrement lié au fonctionnel et pas à la technique, ce qui ne rend que plus robuste l’ensemble des applications qui composent notre SI.

Et vous, quelle est votre architecture ?

Nous sommes en perpétuelle quête d’amélioration, adorons re-challenger nos décisions, et serions donc par conséquent curieux de connaître vos approches en ce qui concerne l’implémentation d’une Clean Architecture et même savoir ce que vous pensez de la notre !

Nous partageons également sur d’autres articles !

Si la Living Documentation vous intéresse, lisez sans plus attendre notre vision à ce propos et comment nous l’avons mise en place.

D’autres articles sont à venir, n’hésitez pas à nous suivre 😉

--

--

Nicolas Poste
Ceetiz
Editor for

CTO @ Ceetiz, passionné par le Software Craftsmanship, les aspects techniques, d'automatisation pour gagner en efficacité, Docker, ...