Clean Architecture

http://cleancoder.com/

Cédric BRASSEUR

  • Ancien étudiant de l'Exia.cesi
  • 4 ans consultant chez Amaris, placé chez Euro Information Dev
  • Auto-entrepreneur depuis début 2020 (Création de sites web, applications mobiles & formateur)

Et vous ?

  • Nom, prénom
  • Etudes réalisées 
  • Autres infos appréciées

Plan du cours

  • Clean Architecture
    • Introduction
    • Utilité et contre exemple
    • Quand l'utiliser ?
    • Le TDD, BDD & le Clean Code (rappels)
    • Composition d'une Clean Architecture
    • Comment en démarrer une facilement ?

  • SOLID & Clean Architecture
  • Aller plus loin...

Clean
Architecture

Objectifs de la formation

  • Réaliser une architecture propre en respectant les principes de Clean Architecture
     
  • Comprendre les enjeux et les projets sur lesquels ce type d'architecture est nécessaire
     
  • Appréhender les principes SOLID
     
  • Analyser un template de Clean Architecture
     
  • Mettre en place un exemple de Clean Architecture sur un projet "simple"

Clean Architecture : 

Introduction

Clean architecture : Définition & utilité

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

Fiabilité

Tout étant testable et même idéalement réalisé en TDD / BDD, la fiabilité est grandement accrue

Clarté

Les avantages pour un projet

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)

Gain en maintenabilité

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)

Utilité et contre-exemple

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)

  • Le nombre de développeurs augmente grandement à chaque release
     
  • Vu comme ça, on se dit que l'entreprise connaît une excellente croissance

Graphique tiré du livre "Clean Architecture" - Robert C. Martin

Utilité et contre-exemple

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

  • Le nombre de lignes de code n'évolue pas tant que ça au fur et à mesure des release...
     
  • C'est soit signe d'un refactoring permanant, soit signe d'une architecture trop complexe pour faire évoluer correctement le projet

Graphique tiré du livre "Clean Architecture" - Robert C. Martin

Utilité et contre-exemple

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)

  • Passer d'environ 10$ la ligne de code à 370$ la ligne de code en 8 release...
     
  • Effrayant, n'est-ce pas ? Et pourtant, c'est souvent le cas, ça l'était un peu dans mon ancienne entreprise. Des milliers de développeurs pour si peu de rendement...

Graphique tiré du livre "Clean Architecture" - Robert C. Martin

Utilité et contre-exemple

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 !

  • On voit une courbe qui tend vers zéro au fur et à mesure des release...
     
  • Frustrant pour les développeurs et effrayant pour les décideurs !
     
  • Le tarif, quant à lui évolue en courbe quasi exponentielle, c'est ici que les décideurs interrompent le projet,... 

Graphiques tirés du livre "Clean Architecture" - Robert C. Martin

Utilité et contre-exemple

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. 

  • L'un des objectif de la Clean Architecture est de ne pas revenir sur du code fonctionnel, mais utiliser des abstractions afin de pouvoir rajouter des fonctionnalités sans impacter l'existant.
     
  • On peut donc imaginer quelque chose de bien plus linéaire en termes de productivité / prix. La courbe démarrant cependant à un niveau sensiblement plus élevé que le schéma "Monthly payroll" de la slide précédente.

Pourquoi ?

  • Comme nous l'avons vu, c'est indépendant des Frameworks, UI, BDD utilisés.
    Donc sur TOUS LES PROJETS
     
  • Nous démarrerons par un simple projet monolithique, puis sur un exemple plus "complexe"

Quand mettre en place une Clean Architecture ?

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 ?

Clean Code

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

TDD

Concepts abordés et associés (rappels)

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.

BDD

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.

Plus concrètement, Clean Architecture, c'est quoi ?

Domaine

Application

Présentation

Infrastructure

Dépendances à respecter le plus possible

  • Indépendant des Frameworks
     
  • Indépendant de l'UI
     
  • Indépendant de la DB
     
  • Indépendant des éléments externes
     
  • Complètement testable

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.

La couche domaine

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.

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)

La couche infrastructure

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...

La couche présentation

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.

L'objectif principal de ces couches ?

Petit exemple et analyse...

Voir le projet clean-architecture-from-scratch

Clean Architectures 

&

Les principes SOLID

S : Single Responsibility Principle

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) : 

  • Service RH
  • Service Cadre
  • Service Employés

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...

O : Open / Closed Principle

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.

O : Open / Closed Principle

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.

L : Liskov Substitution Principle

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 ?

L : Liskov Substitution Principle

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

I : Interface Segregation Principle

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

D : Dependency Inversion Principle

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 : Dependency Inversion Principle

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%

Mise en place d'une Clean Architecture

Clean Architecture c'est quoi ?

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 principes SOLID
     
  • La notion de Composants
     
  • Qu'est-ce qui est un détail dans l'architecture ?
     
  • La notion d'interactor (parfois nommé MediatR)
     
  • Gérer les dépendances correctement

Les grandes lignes à respecter...

Workshop guidé

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.

Workshop - fin

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.

Comment bien démarrer une Clean Architecture

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

Aller plus loin...

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

La notion de Composants

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.

La notion d'Interactors

A vous de lire !

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

Evaluation

Partie pratique

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