Table of Contents
L'architecture propre est plus qu'un mot à la mode dans le développement moderne des logiciels, c'est une approche délibérée et structurée de la conception de systèmes qui résistent au test du temps. L'architecture propre fournit un ensemble de lignes directrices pour l'organisation du code de façon à ce que la logique d'entreprise reste indépendante des influences externes telles que les cadres, les bases de données, les interfaces utilisateur et les services tiers.
Qu'est-ce que l'architecture propre?
L'architecture propre a été popularisée par Robert C. Martin (souvent appelé Oncle Bob) dans son livre Clean Architecture: A Craftsman's Guide to Software Structure and Design et dans une série de blogs. La philosophie fondamentale est de séparer les préoccupations en définissant des couches de responsabilité concentriques, avec la couche la plus interne contenant la logique d'affaires la plus pure et la couche la plus externe traitant des agences externes comme les cadres Web, les bases de données et les composants d'interface utilisateur.
Cette approche n'est pas radicalement nouvelle, elle s'inspire fortement de modèles antérieurs tels que l'architecture hexagonale (Alistair Cockburn), l'architecture d'oignon (Jeffrey Palerme) et le design de domaine (Eric Evans). L'architecture propre apporte à la table un ensemble de règles claires et répétables que toute équipe peut adopter, indépendamment du langage ou du cadre. La règle la plus ironclad est la règle Dépidency : les dépendances ne peuvent pointer que vers l'intérieur.
En appliquant cette règle, les développeurs s'assurent que les changements aux cadres, bases de données ou technologies d'interface utilisateur ne se répercutent pas vers l'intérieur pour corrompre la logique opérationnelle de base.
Principes clés de l'architecture propre
Les principes sont conçus pour guider la prise de décision tout au long du cycle de vie d'un projet. Examinons chaque principe en profondeur.
1. Indépendance des cadres
Un framework est un outil, pas la fondation de votre système. Dans une architecture propre, votre logique d'entreprise ne devrait pas être câblée vers un framework spécifique. Par exemple, si vous construisez une application web avec un framework comme Réact, Angulaire ou Vue, la logique de domaine de base ne devrait pas importer de composants de Réact ou de services Angulaires de référence. Au lieu de cela, le framework devrait être traité comme une préoccupation de couche externe qui se branche dans des interfaces bien définies. Cette indépendance vous permet de mettre à niveau, de remplacer ou même de supprimer le framework sans réécrire le code qui compte le plus.
2. Testabilité
Lorsque les règles d'affaires sont isolées des dépendances externes, elles deviennent triviallement testables. Vous pouvez écrire des tests unitaires pour les entités et utiliser des cas sans faire tourner une base de données, se moquer d'un serveur HTTP, ou charger une interface utilisateur. Cette vitesse et fiabilité des tests encourage les développeurs à tester plus souvent et plus tôt, attraper des bogues avant qu'ils ne s'aggravent. De plus, parce que les couches extérieures (comme les bases de données et les API) sont également conçues autour des interfaces, vous pouvez facilement écrire des tests d'intégration qui vérifient le code de colle sans avoir besoin du système complet en ligne.
3. Séparation des préoccupations
L'architecture propre divise un système en couches, chacune avec une responsabilité distincte. La couche la plus interne (Les entités contient des règles d'entreprise à l'échelle de l'entreprise. La couche suivante vers l'extérieur ([]Use Cases) gère des workflows spécifiques à l'application. De plus, Interface Adapters[ convertit les données entre les formats – par exemple, transformant JSON d'une requête REST en un format que l'utilisateur peut consommer. Enfin, la couche ultrapériphérique (Frameworks and Drivers) se compose de détails comme le pilote de base de données, le serveur web et la boîte à outils d'interface.
4. La règle de dépendance
Cette règle est la colle qui maintient l'architecture ensemble. Elle stipule que les dépendances du code source doivent toujours pointer vers l'intérieur : des couches extérieures vers les couches intérieures. Aucune couche intérieure ne doit jamais connaître une couche extérieure. Dans la pratique, cela signifie que les interfaces définies dans les cas d'utilisation et les entités appartiennent à ces couches intérieures. Les couches extérieures implémentent ces interfaces – elles ne les dictent pas. Par exemple, si une case d'utilisation doit sauver un utilisateur, elle définit une interface [. L'adaptateur de base de données (dans la couche externe) implémente cette interface. Le cas d'utilisation n'importe jamais l'adaptateur de base de données réel; il dépend uniquement de l'interface.
Couches d'architecture propre
Bien que le nombre de couches puisse varier selon le projet, le diagramme d'architecture propre canonique montre quatre anneaux concentriques. La compréhension de chaque couche est essentielle pour appliquer correctement les principes.
Entités (Règles d'entreprise)
Les entités sont la partie la plus stable du système. Elles encapsulent les règles commerciales de base qui s'appliquent à l'ensemble de l'organisation. Par exemple, dans un système bancaire, une entité contiendrait des méthodes comme et qui font appliquer des invariants tels que « l'équilibre ne doit jamais aller au-dessous de zéro ». Les entités sont généralement des objets ou des structures simples sans dépendance externe.
Cas d'utilisation (Règles d'exploitation de la demande)
Les cas d'utilisation définissent comment le système se comporte du point de vue d'un acteur (utilisateur humain, autre système ou minuteur). Ils orchestrent le flux de données à destination et en provenance des entités et ordonnent aux entités d'exécuter leurs règles d'affaires. Par exemple, un appellerait des méthodes sur les entités et persisterait ensuite le résultat par l'intermédiaire d'une interface de dépôt. Les cas d'utilisation sont également exempts de dépendances de cadre. Ils peuvent importer des entités et des interfaces (définies à leur niveau ou à leur niveau) mais jamais des implémentations concrètes de couches extérieures.
Adaptateurs d'interface
Cette couche convertit les données entre le format le plus pratique pour les cas d'utilisation et les entités (généralement des structures de données simples) et le format requis par les organismes externes.
- Contrôleurs qui analysent les requêtes HTTP et appellent le cas d'utilisation approprié.
- Présentateurs qui transforment la sortie de cas d'utilisation en un format approprié pour l'interface utilisateur, comme un modèle de vue.
- P tiers de Database qui implémentent les interfaces de dépôt et traduisent entre les données d'entité et les opérations SQL ou NoSQL.
- Adaptateurs clients API qui appellent des services externes et convertissent les réponses en structures de données internes.
La couche d'adaptateur est l'endroit où vit la plupart des codes "colle". C'est aussi la couche qui tend à changer le plus souvent à mesure que les technologies externes évoluent.
Cadres et moteurs
Le périphérique externe contient toutes les technologies concrètes que le système utilise : le serveur web (ex. Express, Django, Spring Boot), le système de gestion de base de données (ex. PostgreSQL, MongoDB), le cadre d'interface utilisateur (ex. Réaction, Angulaire), etc. Cette couche devrait être aussi mince que possible. Sa tâche principale est de brancher l'application en injectant les implémentations appropriées dans les adaptateurs d'interface et les cas d'utilisation.
Avantages de l'utilisation d'une architecture propre
L'adoption d'une architecture propre procure des avantages concrets et à long terme qui l'emportent sur l'effort initial de structurer le code de cette façon.
- Maintenabilité:[ Lorsque vous devez modifier une fonctionnalité, vous ne modifiez que le cas d'utilisation pertinent et ses entités – pas l'application entière. Parce que les dépendances sont dirigées vers l'intérieur, les changements dans les couches extérieures (comme l'échange d'une base de données) s'encastrent rarement dans la logique d'entreprise.
- Testabilité:[ Comme mentionné précédemment, le faible couplage signifie que vous pouvez tester les règles d'affaires en isolement sans mettre en place un environnement complet.
- Flexibilité: Vous pouvez reporter les décisions sur l'infrastructure. Par exemple, vous pouvez commencer par une simple persistance basée sur des fichiers et ensuite passer à une base de données relationnelles sans réécrire la logique d'affaires, tant que l'interface du dépôt reste la même.
- Scalabilité:[ L'architecture propre ne fait pas automatiquement l'échelle de votre système horizontalement, mais elle supporte l'évolutivité de l'équipe. En séparant les préoccupations en couches, différents membres de l'équipe (ou même différentes équipes) peuvent travailler sur l'interface utilisateur, la base de données et la logique d'affaires simultanément sans marcher sur les orteils.
- Inboarding and collaboration:[ Les nouveaux développeurs peuvent comprendre la structure globale rapidement parce que l'architecture suit un modèle bien connu. Ils peuvent plonger dans des couches spécifiques sans avoir besoin de comprendre la base de code entière.
Pour une plongée plus profonde dans la motivation derrière ce modèle, vous pouvez lire Robert C. Martin , l'original ]]]]]]]]]][FLT:[FLT][F][F
Mise en œuvre d'une architecture propre dans la pratique
La transition vers une architecture propre peut vous intimider, surtout si vous travaillez avec une base de code existante. Les étapes pratiques suivantes vous aideront à commencer.
Étape 1: Identifier et isoler le domaine central
Commencez par examiner votre code existant pour localiser les règles d'affaires pures. Ce sont les parties qui auraient encore du sens si vous avez remplacé la base de données ou l'interface utilisateur demain. Extraire-les dans un module séparé (p. ex., un paquet, un dossier ou un microservice) sans dépendances externes. Ce module deviendra vos entités et utilisera des cas.
Étape 2 : Définir les interfaces pour les interactions externes
Pour chaque opération qui nécessite un système externe (base de données, système de fichiers, réseau, UI), définir une interface à partir de la perspective du noyau. Par exemple, créer une interface avec des méthodes comme et . Ne vous inquiétez pas encore de l'implémentation; l'interface appartient au noyau.
Étape 3 : Construire des adaptateurs qui mettent en œuvre ces interfaces
Maintenant, créez des classes de béton dans les couches extérieures qui implémentent les interfaces. Pour un adaptateur de base de données, il pourrait s'agir d'une classe de dépôt qui utilise votre ORM ou SQL brut. Pour un adaptateur d'interface utilisateur, il pourrait s'agir d'un contrôleur et d'un présentateur qui transforment les données pour une vue Web.
Étape 4: Faire le lien entre tout dans le calque-cadre
Utilisez l'injection de dépendance – que ce soit par un conteneur, une racine de composition ou un câblage manuel – pour connecter les adaptateurs au noyau au démarrage. C'est la responsabilité de la couche externe. Par exemple, dans une application web typique, le point d'entrée principal crée l'adaptateur de base de données, le cas d'utilisation et le contrôleur, puis démarre le serveur. Le noyau reste ignorant de ces classes concrètes.
Étape 5 : Appliquer la refactoration continue
Lorsque vous ajoutez des fonctionnalités, vérifiez continuellement que le nouveau code ne viole pas la règle de dépendance. Utilisez des outils pour faire respecter les limites architecturales (par exemple ArchUnit pour Java ou PHPStan avec des règles personnalisées pour PHP). Extraire régulièrement la logique dupliquée dans les cas et les entités d'utilisation, et pousser le code spécifique au framework vers l'extérieur.
Pièges fréquents à éviter
- Les petits projets sur-ingénierie :[ Une architecture propre ajoute une indirection. Pour une application CRUD simple avec un seul cas d'utilisation et aucun changement attendu, les frais généraux peuvent ne pas en valoir la peine. Utilisez votre jugement.
- Il est étonnamment facile d'importer un framework utility hors de la commodité. Par exemple, en utilisant une annotation ORM=s dans une classe d'entités. Toujours exécuter votre module de base comme bibliothèque autonome d'abord pour vérifier qu'il n'a pas de dépendances externes.
- Créer trop d'interfaces prématurément: Vous n'avez pas besoin d'interface pour chaque classe. Seulement abstrait ce que vous attendez de varier. Commencez par les principales limites externes (base de données, UI, système de fichiers) et généralisez plus tard si nécessaire.
- Ignorer les limites de gestion des erreurs: La façon dont les exceptions sont lancées et prises au-delà des limites des couches nécessite une conception soignée.Les couches internes devraient lancer des exceptions commerciales qui sont significatives pour le noyau.
Exemple du monde réel : Un système de traitement de commande simple
Considérez une application de commerce électronique qui doit passer une commande. Dans une approche d'architecture propre:
- Entités:[ , , avec des règles commerciales comme -Un ordre doit avoir au moins un article et -Un produit -Le stock ne peut pas aller négatif.
- Utiliser Cas:[ reçoit une demande contenant l'ID du client et la liste des produits. Il appelle l'interface pour enregistrer la commande et pour mettre à jour le stock. Il peut également appeler une interface pour alerter le client.
- Adaptateurs d'interface:[ Un extrait des données de la requête HTTP, appelle le cas d'utilisation, puis un transforme le résultat en une réponse JSON. Un implémente en utilisant SQL. Un implémente via une API tierce.
- Frameworks and Drivers: La composition root met en place un serveur web, initialise le pool de connexion de la base de données et file toutes les dépendances ensemble. Le framework web (ex. Express.js ou Spring Boot) n'apparaît que dans cet anneau externe.
Si vous décidez plus tard de passer de PostgreSQL à MongoDB, vous n'avez qu'à écrire un nouveau et à mettre à jour la racine de composition. Le cas d'utilisation et les entités restent intacts. Si vous voulez ajouter un nouveau canal de notification comme SMS, vous créez un autre adaptateur et l'enregistrez – encore une fois sans modifier le noyau.
Quand devriez-vous adopter une architecture propre?
L'architecture propre n'est pas une balle d'argent. Il est le plus précieux dans les projets avec une complexité modérée à élevée, une longue durée de vie attendue, ou un domaine d'affaires qui est au centre de la réussite de l'entreprise.
- Vous prévoyez de fréquents changements aux règles d'affaires.
- Le système doit s'intégrer à de multiples bases de données ou services externes susceptibles de changer.
- Vous avez une équipe de développeurs qui doivent travailler en parallèle.
- Vous construisez des systèmes qui servent plusieurs interfaces client (web, mobile, bureau) à partir du même moteur.
D'autre part, pour les petits prototypes, les scripts uniques ou les projets à cycle de vie très court, une architecture plus simple (comme une structure MVC plate) peut être plus pragmatique. Vous pouvez toujours refactorer vers une architecture propre à mesure que le projet grandit.
Conclusion
En appliquant la règle de dépendance et en séparant les préoccupations en couches distinctes, les développeurs peuvent isoler la logique d'affaires de base de l'inévitable crible des technologies externes. L'investissement initial dans la conception des interfaces et l'organisation du code est rentable lorsque vous devez ajouter des fonctionnalités, changer de base de données ou à bord de nouveaux membres de l'équipe. Bien que cela ne soit pas approprié pour chaque projet, ses principes – indépendance des cadres, testabilité, séparation des préoccupations et règle de dépendance – offrent un plan de travail précieux que chaque développeur devrait comprendre.
Pour plus de détails sur la modélisation de domaine et l'architecture propre dans des langages de programmation spécifiques, vous pouvez vous référer à Domain-Driven Design Community ou à l'article explicite sur l'architecture d'Herberto Graça qui relie plusieurs motifs.