http://cleancoder.com/
Clean
Architecture
Introduction
Indépendant des Frameworks
Le Clean Architecture est un moyen de structurer ses projets afin d'optimiser la maintenabilité, la testabilité et la clarté de votre application.
Indépendant de la base de données
Indépendant du front (UI)
Indépendant de tout ce qui est externe
Et surtout... Testable intégralement
Tout étant testable et même idéalement réalisé en TDD / BDD, la fiabilité est grandement accrue
Le fait de décomposer son projet et avoir une structure "Clean" permet d'améliorer la clarté du projet, simplifie les corrections de bug (et leur détection)
En respectant les principes que nous verrons par la suite, vous gagnez également en maintenabilité (Nous en reparlerons avec les principes SOLID). De même en gérant bien les dépendances, les évolutions sont censées nécessiter moins de modifications du code existant (Gestion des dépendances)
Analysons ensemble les courbes de coût / temps nécessaire à la mise en place de nouvelles fonctionnalités au fur et à mesure des releases.
On démarre par l'analyse du nombre de personnes dans une entreprise (qui ne fait pas de Clean Architecture)
Graphique tiré du livre "Clean Architecture" - Robert C. Martin
Maintenant, analysons la productivité évaluée pour chaque release, afin de nous assurer que le nombre de développeurs grandissant correspond aussi à une productivité accrue
Graphique tiré du livre "Clean Architecture" - Robert C. Martin
Le graphique suivant permet de connaître le coût par ligne de code (donc en fonction du nombre de ressources produisant sur le projet)
Graphique tiré du livre "Clean Architecture" - Robert C. Martin
Pour que ce soit plus parlant, voici le graphique estimé de productivité par release, en gardant en tête que pourtant le nombre de ressources sur le projet a drastiquement augmenté au fil du temps !
Graphiques tirés du livre "Clean Architecture" - Robert C. Martin
Malheureusement, je n'ai pas l'équivalent pour le même projet en utilisant une Clean Architecture, mais bien qu'il soit certain que le coût de démarrage est plus élevé, il est aussi certain que le coût pour la maintenabilité et l'évolutivité du projet ne serait pas exponentiel.
Pourquoi ?
Concrètement, il est toujours bénéfique de mettre en place une Clean Architecture, mais disons que plus le projet est volumineux et amené à évoluer dans le temps, plus les bénéfices en seront accrus.
Sur quoi l'appliquer ?
L'objectif étant de rendre le code maintenable et efficace, il est important de réaliser du Clean Code lorsque l'on fait une Clean Architecture
Rappelez-vous, le TDD force la création d'un test unitaire afin d'en faire découler le code projet.
Une Clean Architecture est une architecture qui permet de réaliser des tests clés en main.
Bien que l'objectif et les tests sont différents, le Behavior Driven Development est également un atout lors de la mise en place d'une Clean Architecture.
Domaine
Application
Présentation
Infrastructure
Dépendances à respecter le plus possible
Elle contient les entités, les énumérations, les exceptions, les interfaces, les types et la logique spécifique à la couche domaine.
De plus, cette couche contient les interfaces de Repository que vous allez implémenter dans la couche Infrastructure (Car elle va l'utiliser dans les services, en général)
C'est l'élément central qui contient la Business Logic, l'objectif est de ne dépendre de rien, afin que des modifications dans les autres couches impactes le moins possible cette couche.
Cette couche contient toute la logique applicative. Elle dépendant du domaine mais n'a aucune autre dépendance. Cette couche définie les interfaces qui sont implémentées en dehors de la couche.
Par exemple, si l'application a besoin d'un service de notification, une nouvelle interface doit être ajoutée à l'application et l'implémentation sera créée via la couche infrastructure, avec une classe Repository (dont l'interface est dans Domain), en utilisant un DBContext par exemple.
Le service en lui même sera quant à lui implémenté également dans la couche Application.
Cette couche contient les classes pour accéder aux ressources externes, telles qu'un système de fichier, un web service, un service SMTP, une base de données, etc... Ces classes doivent être basées sur des interfaces définies dans la couche application.
Il arrive souvent aussi qu'il y ai une couche Infrastructure IoC (Inversion of Control) L'objectif de ce IoC est de bien maîtriser et centraliser la gestion des dépendances du projet (et ainsi respecter les principes SOLID vu plus tard dans la formation)
Dans notre exemple, cette couche est une SPA (Single Page Application). Cette couche dépend de deux autres couches : La couche Application et la couche Infrastructure. Cependant, les dépendances d'infrastructure permettent uniquement de gérer l'injection de dépendances (En utilisant potentiellement l'IoC).
C'est pourquoi seulement "Startup.cs" doit référencer l'infra.
Elle contient les vues, les controllers, le css, les médias, la Startup class...
Comme abordé précédemment, l'objectif final de la mise en place de ces différentes couches est essentiellement de structurer le projet, le rendre complètement testable et surtout faire en sorte qu'une modification dans une couche telle que Presentation, n'impacte pas forcément toutes les autres couches.
Voir le projet clean-architecture-from-scratch
&
Les principes SOLID
C'est assez explicite par son nom, mais ça doit être précisé pour éviter de passer à côté de quelque chose d'important : On veut effectivement que chaque élément ne soit responsable que d'une chose et qu'il la fasse bien. Mais on souhaite aussi séparer les "choses faites" par acteur, c'est important pour éviter les soucis.
Prenons un exemple...
Vous avez trois services (d'entreprise) :
Si vous réalisez une seule méthode pour calculer le salaire des employés, vous ne respectez pas le SRP. Imaginez que le calcul change pour le service Cadre uniquement, si le code a été factorisé, vous allez modifier le calcul de salaire de tous les services et causer des effets de bords sur les salaires des RH et Employés...
Votre programme doit être ouvert aux extensions mais fermé aux modifications. C'est à dire que l'on va utiliser des abstractions afin de faire évoluer le code existant plutôt que de le modifier ! Ainsi que gérer les injections de dépendances dans un sens particulier pour diminuer les impacts en modifications.
Comment gérer les dépendances ?
L'objectif est de respecter ce que l'on a vu dans la partie précédente, afin de rendre la partie Domain la moins sujette aux modifications impactantes, contrairement à la partie Presentation qui elle risque des modifications régulières mais ayant peu d'impacts sur le système.
Prenons un exemple...
Dans cet exemple, l'objectif est d'afficher des stats sur un support différent.
Il faut donc noter surtout le sens des dépendances ainsi que les interfaces utilisées pour cloisonner, et savoir que l'on pointe le plus possible vers l'Interactor (Buisness Rules) pour que cette partie soit la moins ouverte aux modifications.
Les interfaces doivent êtres utilisées de manière à s'assurer que chaque cas d'utilisation correspond bien aux attentes. Pour mieux comprendre prenons un contre-exemple bien connu.
Prenons un contre-exemple...
Admettons que nous avons une projet qui permet un User de travailler avec un Rectangle, puis par la suite on souhaite le faire travailler également avec un carré.
Ici, on peut imaginer que ça peut factoriser le code de réaliser quelque chose de ce style, mais ça ne respecte pas le LSP.
Vous voyez pourquoi ?
Les interfaces doivent êtres utilisées de manière à s'assurer que chaque cas d'utilisation correspond bien aux attentes ET soient substituables (remplaçable)
Prenons un exemple architectural...
Pour le projet Collect&Verything, vous passez par des API REST, admettons une URI comme ceci :
collect.com/enterprise/cora/pickupaddress/57000 Metz.../products/123
Imaginons qu'une entreprise (Click) réutilise notre système sans lire correctement les specs et décide de mettre en place ce système, mais en utilisant pickup à la place de pickupaddress.
Ceci nous forcerait à avoir un if (enterprise == "Click") dans le code et dont des conditions à gérer. Ici, bien utiliser une interface permettrait d'éviter ce problème architectural
Ce principe a pour objectif d'éviter de vous trimballer des choses dont vous ne dépendez pas (dans votre classe).
Prenons un exemple...
Prenons ce diagramme de classes et imaginons que OPS soit une liste d'opérations et que chaque User utilise l'opération correspondant à son chiffre (U1 => op1,...)
Il serait nécessaire d'ajouter des interfaces afin de limiter les dépendances des Users vers des opérations qui ne les intéressent pas
Ce principe dit d'abord que les programmes les plus flexibles sont les programmes dont le source code dépend d'abstractions et non d'éléments concrets. Plus directement, chaque import devrait être une abstraction et jamais une classe concrète. (Bien que ce soit impossible dans les faits, c'est une règle à appliquer au maximum)
Quel est l'objectif ?
Les éléments concrets sont plus volatiles & un changement dans une classe concrète nécessite forcément une modification dans la classe qui en dépend. Ce n'est pas (forcément) vrai dans le cas d'une modification d'abstraction.
Ne jamais faire référence à un élément volatile !
D'accord, mais comment faire ça ?
Pour éviter ces dépendances que l'on souhaite ne jamais mettre dans notre code, nous devons utiliser les "Abstract Factories",
La ligne rose est une barrière entre les abstractions et les implémentations concrètes
Prenons un exemple...
Remarquez les dépendances de chaque côté de la ligne rose sont dans des sens opposés (d'où le nom de DIP)
Par contre, l'élément concret ici ne respecte pas le DIP, on répète que le DIP n'est pas applicable à 100%
Plus globalement, la Clean Architecture est une suite de règles à respecter en mettant en place l'architecture de son projet afin de le rendre le plus imperméable aux modifications foireuses, tout en ayant un projet testé de bout en bout (TDD, BDD, etc...)
Les grandes lignes à respecter...
Vous allez suivre un tutoriel guidé sur une mise en place de Clean Architecture simple (que j'ai fait intégralement, donc n'hésitez pas à remonter les éventuelles soucis dans le sujet).
Je serai présent pour vous aider, je vais d'abord vous montrer le rendu attendu
Envoyer le fichier workshop_CA_simple.md
Ce workshop va prendre pas mal de temps, même s'il est très guidé, n'hésitez pas à me demander de vous aider si besoin.
Maintenant, je vous demande de vous débrouiller pour ajouter un autre service, par exemple, ajoutez un gestion des Classes (les promos) pour l'intégrer complètement dans la Clean Architecture fraichement créée. Le but est d'afficher une page disponible uniquement en étant connecté contenant le listing des classes (2/3 insérées vite fait en BDD suffiront !)
Vous devriez seulement avoir à resuivre quelques étapes du tutoriel, ça vous permettra de comprendre comment utiliser cette architecture et ajouter y du nouveau code.
Prenez bien le temps de revoir ce qui doit être dans quel couche.
Pour démarrer une Clean Architecture, on peut soit tout implémenter soit même, soit utiliser un template pour aller plus vite.
Nous allons mettre en place (facilement) un template réalisé par Jason Taylor (https://github.com/jasontaylordev/)
C'est un template .NET Core 6 en Single Page App (SPA) avec Angular & ASP.NET en asynchrone (nous verrons l'asynchronisme en Décembre)
(Autre template : https://github.com/ardalis/cleanarchitecture)
Pour se faire, il suffit d'aller sur https://github.com/jasontaylordev/CleanArchitecture
Soit vous utilisez directement le template github.
Soit on execute les commandes suivantes :
dotnet new --install Clean.Architecture.Solution.Template # Récupération - Une fois
dotnet new ca-sln # Créer la structure du projet
cd src/WebUI # Se déplacer sur le dossier de démarrage
dotnet run # Démarrer le projet
Lectures et autres conseils
La notion de composant est importante dans la Clean Architecture, nous avons parlé de comment arranger la structure de nos projets. La notion de composant permet de voir comment organiser et réaliser correctement les éléments composant le projet
Brièvement, les composants permettent de faire des briques de code réutilisables dans votre projet.
Mais il y a des risques et des règles à appliquer également.
Interactor, définition et utilité :
Un interacteur est un Design Pattern qui n'a rien à voir avec la Business Logic (je précise la slide car la confusion peut se trouver dans de nombreuses sources, dont une de mes slides précédente). C'est une extension du Pattern Command, chaque objet de Buisiness Logic est considéré comme une boîte noire, une simple instruction à exécuter pour le client. Découplant l'objet qui invoque les opération de l'objet qui sait comment les exécuter.
Afin de mieux comprendre le Clean Architecture en détails, je vous propose de vous envoyer une ressource qui contient toutes les informations qu'il vous faut.
Mais sachez que ce que l'on a vu ensemble est une bonne introduction à la Clean Architecture, ce bouquin propose des concepts avancés qui ne me sont pas encore tous connus (Ceci ne m'empêchant pas de pouvoir en mettre une en place en cas de besoin)
Envoyer le pdf Clean Architecture
Réaliser un contenu supplémentaire sur la Clean Architecture (à minima lecture)
Par exemple, vous pouvez faire en sorte de lister les étudiants, puis essayez de réaliser l'opération d'insertion