Be Software Architecture Wise
Be Wise Apps

Read in English · Leer en español · Auf Deutsch lesen

Section 3.4 sur 3.8 · Intermédiaire · 25 min de lecture

Gérer les dépendances : sens, cycles et stabilité

Leçon vidéo

5 min · narré en anglais, avec sous-titres
Écouter seulement (audio)
Lire la transcription

Chaque dépendance a un sens, et ce sens décide qui est dérangé par les changements de qui. Dans cette leçon, vous allez découvrir trois principes pour les dépendances entre composants, les métriques qui les sous-tendent, et comment les vérifier automatiquement.

Pensez à un immeuble. Les étages supérieurs reposent sur les étages inférieurs, et les fondations ne reposent sur rien de ce qui se trouve au-dessus. Vous pouvez refaire la décoration du dernier étage sans toucher aux fondations. Mais si vous creusez sous les fondations, tout ce qui est au-dessus est menacé. Imaginez maintenant deux étages qui se soutiennent mutuellement. Vous ne pourriez jamais reconstruire ni l’un ni l’autre. Un bon logiciel place les parties qui changent souvent au-dessus de parties solides et rarement modifiées, et ne laisse jamais deux parties se soutenir l’une l’autre en boucle.

Représentez une dépendance par une flèche qui va du module qui a besoin de quelque chose vers le module dont il a besoin : de Paiement vers Tarification. Si Tarification change, Paiement devra peut-être changer. Si Paiement change, Tarification n’en a cure. Les changements se propagent à contre-sens des flèches. Entre services, le sens se manifeste sous forme de connaissance. Si Commandes appelle l’API de Facturation, Commandes dépend de Facturation. Si Commandes publie un événement que Facturation consomme, la flèche s’inverse, et Commandes ne sait même pas que Facturation existe.

Le premier principe de Robert C. Martin est le principe des dépendances acycliques : pas de cycles entre composants. Ici, Commandes appelle Notifications pour envoyer une confirmation, et Notifications rappelle Commandes pour lire la commande. Aucun des deux ne peut être construit, testé ou mis en production sans l’autre. La première solution inverse une dépendance. Notifications déclare une petite interface, order lookup, qui décrit exactement ce dont il a besoin, et Commandes l’implémente. Notifications ne dépend plus de rien dans Commandes. L’interface appartient au module qui l’utilise. La seconde solution extrait ce dont les deux côtés ont besoin, ici les événements de commande, dans un nouveau composant dont les deux dépendent. Il ne dépend d’aucun des deux, il est donc plus stable que chacun d’eux.

Le deuxième principe est le principe des dépendances stables : dépendez dans le sens de la stabilité. Un composant censé être facile à changer ne devrait pas être utilisé par un composant difficile à changer. Martin le mesure par l’instabilité : les dépendances sortantes, divisées par l’ensemble des dépendances, entrantes plus sortantes. Zéro signifie uniquement des dépendances entrantes : stabilité maximale, et des responsabilités envers beaucoup. Un signifie uniquement des dépendances sortantes : rien ne dépend du composant, il est donc libre de changer. Le long de chaque flèche, l’instabilité doit donc diminuer. Chaque composant devrait dépendre de composants plus stables que lui.

Le troisième principe est le principe des abstractions stables : un composant doit être aussi abstrait qu’il est stable. Le degré d’abstraction est la part de ses classes qui sont des classes abstraites ou des interfaces. Reportez le degré d’abstraction en fonction de l’instabilité, et vous obtenez ce carré. La diagonale est la séquence principale : stable et abstrait en haut à gauche, instable et concret en bas à droite. La distance à cette droite va de zéro, sur la droite, à un, dans un coin éloigné. Près du coin inférieur gauche se trouve la zone de douleur : stable, parce que beaucoup de choses en dépendent, mais concret, si bien que les changements sont nécessaires et douloureux. Un schéma de base de données que chaque module lit directement se trouve là. Près du coin supérieur droit se trouve la zone d’inutilité : des abstractions dont rien ne dépend, souvent des interfaces laissées à l’abandon après un refactoring.

Essayons sur une boutique. Son composant d’API de paiement a six dépendances entrantes et une sortante : son instabilité vaut donc un septième, environ zéro virgule quatorze. Quatre de ses cinq classes sont des interfaces. Il se trouve presque exactement sur la séquence principale. Le cœur des commandes a huit dépendances entrantes et deux sortantes : son instabilité vaut donc zéro virgule deux. Mais seules deux de ses vingt classes sont abstraites, si bien que sa distance à la séquence principale vaut zéro virgule sept. Stable, mais concret : il est dans la zone de douleur. L’interface utilisateur web dépend de neuf autres composants, et rien ne dépend d’elle. Totalement instable, totalement concrète, et pile sur la droite. Le remède déplace les types qu’utilisent les dépendants du cœur des commandes dans un petit composant surtout abstrait, et laisse le code concret dans un composant séparé. Les deux finissent à une distance de zéro virgule vingt-cinq. Les chiffres sont des invitations à regarder, pas des objectifs.

L’organisation en couches est la plus ancienne règle de dépendance. Dans l’organisation en couches stricte, chaque couche ne peut utiliser que la couche juste en dessous d’elle. Chaque couche cache tout ce qui se trouve en dessous d’elle, mais les couches intermédiaires risquent de se contenter de relayer les appels. Dans l’organisation en couches souple, une couche peut utiliser n’importe quelle couche inférieure. Cela supprime le code de relais, mais un changement en bas peut affecter toutes les couches au-dessus. Quelles que soient les règles que vous choisissez, vérifiez-les automatiquement. Des outils comme ArchUnit pour Java, dependency-cruiser pour JavaScript et import-linter pour Python s’exécutent dans le build et le font échouer quand une règle est enfreinte.

La section complète contient des exemples de règles pour chaque outil et un guide de décision pour corriger une dépendance indésirable. Ensuite, dans la section trois point cinq, nous appliquerons les principes SOLID aux composants et aux services. À très vite.

Ce qu’il faut savoir

Pourquoi le sens compte

Une dépendance existe quand un module a besoin d’un autre pour compiler, s’exécuter ou se comporter correctement. Représentez-la par une flèche qui va du module qui dépend vers le module dont il dépend : de Paiement vers Tarification. La flèche vous apprend quelque chose d’important sur le changement. Si Tarification change d’interface ou de comportement, Paiement devra peut-être changer ; si Paiement change, Tarification n’en a cure. Les changements se propagent à contre-sens des flèches. Le sens de chaque dépendance est donc une décision sur qui sera dérangé par les changements de qui.

Les architectes s’intéressent à deux sortes de sens faciles à confondre. Le flot de contrôle indique qui appelle qui à l’exécution. La dépendance de code source indique qui importe, référence ou compile contre qui. En général, les deux pointent dans le même sens, mais ce n’est pas obligatoire. Un framework appelle votre code sans en dépendre (la section 0.3 a appelé cela l’inversion de contrôle), et grâce à une interface, un module de haut niveau peut appeler un module de bas niveau alors que la dépendance de code source pointe dans l’autre sens. Cette liberté de choisir le sens de la dépendance de code source indépendamment du flot de contrôle est l’outil le plus puissant de cette section.

Entre services, le sens se manifeste sous forme de connaissance. Si Commandes appelle l’API de Facturation, Commandes dépend de Facturation et doit changer quand l’API de Facturation change. Si Commandes publie un événement que Facturation consomme, la dépendance s’inverse : Facturation dépend du contrat d’événement de Commandes, et Commandes ne sait même pas que Facturation existe (section 3.2). Choisir entre un appel et un événement, c’est donc en partie choisir le sens d’une dépendance.

Le principe des dépendances acycliques

Robert C. Martin a formulé trois principes pour les dépendances entre composants, d’abord dans des articles au milieu des années 1990, puis dans Agile Software Development: Principles, Patterns, and Practices (2002) et Clean Architecture (2017). Le premier, le principe des dépendances acycliques (ADP), dit que le graphe des dépendances entre composants ne doit contenir aucun cycle : ce doit être un graphe orienté acyclique.

Un cycle de dépendances, par exemple Commandes dépend de Notifications, Notifications dépend de Clients et Clients dépend de Commandes, transforme trois composants en un seul. Aucun ne peut être compilé ou testé sans les autres ; un changement dans l’un peut faire le tour de la boucle et revenir jusqu’à lui ; aucun ne peut être mis en production seul ; et personne ne peut dire lequel construire en premier. Martin introduit le principe avec ce qu’il appelle le syndrome du lendemain matin : vous rentrez chez vous avec du code qui fonctionne et revenez le lendemain pour le trouver cassé, parce que quelqu’un d’autre a modifié quelque chose dont vous dépendez. Son remède consiste à découper le système en composants mis en production séparément, et cela ne fonctionne que si les dépendances entre eux ne contiennent aucun cycle. Les cycles empêchent aussi d’extraire plus tard un composant sous forme de bibliothèque ou de service, car il entraîne toute la boucle avec lui.

Le Java Platform Module System impose l’ADP entre modules : la Java Language Specification fait une erreur de compilation du fait qu’un module dépende, directement ou indirectement, de lui-même. La plupart des autres langages autorisent les cycles entre paquets ; les équipes les détectent donc avec des outils. Les cycles entre classes à l’intérieur d’un petit composant sont moins nuisibles, et Martin Fowler, entre autres, les y tolère ; le principe compte surtout entre des composants que des personnes différentes construisent et mettent en production.

Casser les cycles

Tout cycle peut être cassé, et il existe deux techniques classiques. Le schéma ci-dessous les montre toutes deux pour un cycle dans lequel Commandes appelle Notifications pour envoyer une confirmation, et Notifications rappelle Commandes pour lire les détails de la commande.

Inversion des dépendances. Notifications déclare une interface qui décrit exactement ce dont il a besoin, par exemple OrderLookup avec une seule méthode, et Commandes l’implémente. La flèche de Notifications vers Commandes pointe maintenant dans l’autre sens : Commandes dépend de Notifications (pour l’appeler et pour implémenter son interface), et Notifications ne dépend de rien dans Commandes. C’est l’inversion des dépendances, que la section 3.5 traite comme le D de SOLID. Le détail clé est la propriété : l’interface appartient au module qui l’utilise, et non à celui qui l’implémente.

Extraire un nouveau composant. Repérez ce dont les deux côtés ont réellement besoin l’un de l’autre, souvent un petit modèle ou un contrat commun, et placez-le dans un nouveau composant dont les deux dépendent. Ici, Commandes publie un type d’événement défini dans un composant événements de commande, et Notifications lit les détails de la commande dans l’événement au lieu de rappeler Commandes. Le nouveau composant est utilisé par les deux et ne dépend d’aucun, si bien qu’il est plus stable que chacun d’eux : c’est exactement ce que demande le principe suivant.

Deux façons de casser un cycle

Cycle de dépendances entre Commandes et Notifications, cassé de deux façons : par inversion des dépendances et par extraction d’un nouveau composantCycleCommandeNotificationsenvoielit commandeaucun ne se construit, se testeni ne se livre sans l’autre1. Inverser une dépendanceCommandeOrderLookupNotificationsimplémenteinterface propre à Notifications2. Extraire un composantCommandeNotifi-cationsÉvén. commande(modèle commun)les deux dépendent d’uncomposant neuf et stable1. Commandes appelle Notifications, qui exprime son besoin par une interface ; Commandes l’implémente.2. La partie commune passe dans son propre composant ; Notifications ne lit plus Commandes.Les flèches vont du module qui dépend vers le module dont il dépend. Pointillés : implémente.
On peut casser un cycle en inversant une dépendance au moyen d’une interface qui appartient à son utilisateur, ou en extrayant ce que les deux côtés partagent dans un nouveau composant.

Le principe des dépendances stables et l’instabilité

Certains composants changent souvent (interfaces utilisateur, intégrations avec des partenaires, nouvelles fonctionnalités) ; d’autres devraient changer rarement (concepts centraux du domaine, contrats partagés, gestion de l’argent). Le principe des dépendances stables (SDP) dit : dépendez dans le sens de la stabilité. Un composant censé être facile à changer ne devrait pas être utilisé par un composant difficile à changer, sinon il deviendra lui aussi difficile à changer.

Martin mesure la stabilité par les dépendances, et non par la fréquence à laquelle le code est modifié. Un composant dont beaucoup d’autres dépendent est stable, au sens où il est coûteux à changer : chaque changement risque de casser ce qui dépend de lui. Un composant qui dépend de beaucoup d’autres et dont rien ne dépend est instable : rien ne l’empêche de changer, et ses dépendances peuvent l’y obliger. À partir du couplage afférent (Ca, entrant) et du couplage efférent (Ce, sortant) de la section 3.2, l’instabilité vaut :

I = Ce / (Ca + Ce)

I va de 0 à 1. I = 0 signifie uniquement des dépendances entrantes : stabilité maximale, des responsabilités envers beaucoup, aucune dépendance. I = 1 signifie uniquement des dépendances sortantes : instabilité maximale, rien ne dépend de lui. Exprimé sous forme de métrique, le SDP dit que I doit diminuer le long de chaque flèche de dépendance : chaque composant doit dépendre de composants dont le I est plus faible que le sien. Une dépendance d’un composant stable (I = 0,1) vers un composant volatil (I = 0,8) est un signal d’alerte ; le remède habituel est là encore l’inversion des dépendances, en plaçant une interface à un endroit stable.

Le principe des abstractions stables et la séquence principale

La stabilité a un coût : un composant dont le I est proche de 0 est difficile à changer. S’il est rempli de code concret, le système devient rigide. Le principe des abstractions stables (SAP) résout ce problème : un composant doit être aussi abstrait qu’il est stable. Les composants stables doivent se composer surtout d’interfaces et de types abstraits, pour que leur comportement puisse être étendu par de nouvelles implémentations ailleurs sans les modifier ; les composants instables peuvent être concrets, car ils sont de toute façon faciles à changer. Ensemble, le SDP et le SAP reviennent au principe d’inversion des dépendances appliqué à des composants entiers : les dépendances pointent vers la stabilité, et la stabilité va de pair avec l’abstraction.

Le degré d’abstraction se mesure par A = Na / Nc, où Na est le nombre de classes abstraites et d’interfaces du composant et Nc le nombre total de classes. A = 0 signifie entièrement concret ; A = 1 signifie uniquement des abstractions. Reporter A en fonction de I donne un carré. La droite qui va de (I = 0, A = 1) à (I = 1, A = 0) est la séquence principale (main sequence) : stable et abstrait à une extrémité, instable et concret à l’autre, avec des mélanges équilibrés entre les deux. La distance à cette droite vaut :

D = |A + I - 1|

D va de 0 (sur la droite) à 1 (dans un coin éloigné). Le coin proche de (0, 0) est la zone de douleur : stable, parce que beaucoup de choses en dépendent, et concret, si bien que tout changement est à la fois nécessaire et douloureux. Un schéma de base de données que chaque module lit directement se trouve là. Martin note que certains composants y ont leur place sans dommage, comme une bibliothèque utilitaire concrète et mature qui n’a jamais besoin de changer ; la zone ne fait mal que pour les composants volatils. Le coin proche de (1, 1) est la zone d’inutilité : des abstractions dont rien ne dépend, typiquement des interfaces laissées à l’abandon après un refactoring ou conçues pour une extension qui n’est jamais venue.

La séquence principale

Degré d’abstraction en fonction de l’instabilité, avec la séquence principale de (0 ; 1) à (1 ; 0), la zone de douleur près de (0 ; 0), la zone d’inutilité près de (1 ; 1), et trois composants placés0101Instabilité I = Ce / (Ca + Ce)Abstraction AZone dedoul.Zoned’inutilitéséq. principale : A + I = 1Composantspayments-apiI 0,14 ; A 0,80D 0,06orders-coreI 0,20 ; A 0,10D 0,70web-uiI 1,00 ; A 0,00D 0,00point rouge : horsséq. principalepayments-apiorders-coreweb-ui
Les composants proches de la séquence principale équilibrent degré d’abstraction et stabilité ; la zone de douleur contient du code stable et concret, et la zone d’inutilité des abstractions que personne n’utilise.

Exemple commenté

Exemple commenté : calculer I, A et D pour trois composants

Une boutique compte trois composants. Selon la définition de Martin, Ca compte les classes extérieures au composant qui dépendent de classes situées à l’intérieur, et Ce compte les classes intérieures qui dépendent de classes extérieures. Les chiffres proviennent d’un outil d’analyse des dépendances.

payments-api (5 classes, dont 4 interfaces)Ca = 6, Ce = 1. I = 1 / (6 + 1) = 0,14. A = 4 / 5 = 0,80. D = |0,80 + 0,14 - 1| = 0,06.
orders-core (20 classes, dont 2 abstraites)Ca = 8, Ce = 2. I = 2 / (8 + 2) = 0,20. A = 2 / 20 = 0,10. D = |0,10 + 0,20 - 1| = 0,70.
web-ui (12 classes, aucune abstraite)Ca = 0, Ce = 9. I = 9 / (0 + 9) = 1,00. A = 0 / 12 = 0,00. D = |0 + 1 - 1| = 0,00.
Vérifier le SDP le long des flèchesweb-ui (1,00) dépend d’orders-core (0,20) et de payments-api (0,14) ; orders-core (0,20) dépend de payments-api (0,14). I diminue le long de chaque flèche : le SDP est donc respecté.
Diagnosticorders-core a D = 0,70 : stable (beaucoup de dépendants) mais concret. Si ses règles changent souvent, chaque changement met en risque huit classes dépendantes. Il se trouve dans la zone de douleur.
Remède et nouveau calculExtrayez les quatre types qu’utilisent ses dépendants dans orders-api (3 interfaces et 1 type valeur : A = 0,75 ; Ca vaut 8 ou plus, puisque les classes d’implémentation en dépendent désormais aussi, et Ce = 0, donc I = 0 ; D = |0,75 + 0 - 1| = 0,25). Les 16 classes concrètes restantes deviennent orders-impl, dont seul dépend le câblage de démarrage (Ca = 1) et qui dépend d’orders-api et de deux autres composants (Ce = 3) : I = 3 / 4 = 0,75, A = 0, D = 0,25.

Le remède rapproche nettement les deux parties de la séquence principale (de 0,70 à 0,25 chacune). Les dépendants s’appuient désormais sur un composant petit, stable et surtout abstrait, et le code concret volatil se trouve là où il est peu coûteux à changer.

Les chiffres sont des heuristiques, pas des objectifs. Un D de 0,70 est une raison d’aller voir, pas la preuve d’un problème ; si orders-core ne changeait jamais, il ne ferait aucun tort là où il est.

Organisation en couches stricte et souple

L’organisation en couches (layering) est la règle de dépendance la plus ancienne et la plus courante : disposer les modules en couches, comme la présentation, l’application ou le domaine, et la persistance, et n’autoriser chaque couche à utiliser que les couches situées en dessous. Les couches inférieures ne savent rien des couches supérieures, si bien qu’on peut ajouter une nouvelle interface utilisateur sans modifier le domaine. L’organisation en couches est un style architectural à part entière (section 5.2) ; ici, ce qui nous intéresse est la règle de dépendance qu’elle impose.

Dans l’organisation en couches stricte (fermée), chaque couche ne peut utiliser que la couche immédiatement inférieure. Chaque couche cache alors tout ce qui se trouve en dessous d’elle, et remplacer une couche inférieure n’affecte qu’une seule autre couche ; le prix à payer est du code de simple transmission, où la couche intermédiaire se contente de relayer des appels auxquels elle n’a rien à ajouter. Dans l’organisation en couches souple (ouverte), une couche peut utiliser n’importe quelle couche inférieure. Cela supprime le code répétitif, mais un changement dans la couche du bas peut alors affecter toutes les couches au-dessus. Beaucoup de systèmes réels mélangent les deux, en marquant certaines couches comme ouvertes, par exemple une couche partagée de journalisation ou d’utilitaires que tout le monde peut utiliser.

L’organisation en couches classique a une faiblesse notoire : la couche domaine dépend de la couche persistance, si bien que les règles métier les plus importantes dépendent de détails de base de données. Les architectures hexagonale, en oignon et Clean Architecture (section 5.6) appliquent l’inversion des dépendances pour y remédier : le domaine possède des interfaces comme OrderRepository, et le code de persistance les implémente, si bien que toutes les dépendances de code source pointent vers l’intérieur, vers le domaine.

Organisation en couches stricte et souple

Couches strictes : appels seulement vers la couche juste en dessous ; couches souples : appels vers toute couche inférieureStrictes (fermées)PrésentationApplication / domainePersistancechaque couche n’utilise que la suivanteSouples (ouvertes)PrésentationApplication / domainePersistancesauttoute couche inférieure est utilisable
L’organisation en couches stricte n’autorise les appels que vers la couche immédiatement inférieure ; l’organisation souple permet aussi à une couche de sauter des couches situées en dessous.

Vérifier automatiquement les règles de dépendance

Les règles de dépendance écrites uniquement dans un wiki sont enfreintes en quelques semaines. Le moyen fiable de les faire respecter est de les exprimer sous forme de vérifications exécutables qui tournent dans le build et le font échouer quand une règle est enfreinte. Une telle vérification est une fonction d’aptitude (fitness function) architecturale, une idée présentée dans la section 2.8 et développée dans la section 11.3. Trois outils open source largement utilisés couvrent les principaux écosystèmes.

ArchUnit (Java et autres langages de la JVM) vous permet d’écrire des règles d’architecture sous forme de tests unitaires ordinaires : quels paquets peuvent accéder à quels autres, définitions de couches, et absence de cycles entre tranches de la base de code. dependency-cruiser (JavaScript et TypeScript) analyse les imports et les valide par rapport aux règles d’un fichier de configuration, et sa configuration de départ interdit déjà les dépendances circulaires. import-linter (Python) vérifie des contrats déclarés dans un fichier de configuration ; ses types de contrats couvrent notamment les imports interdits, les couches, l’indépendance entre modules frères, les modules protégés et l’acyclicité entre modules frères.

@ArchTest
static final ArchRule layers = layeredArchitecture()
    .consideringAllDependencies()
    .layer("Web").definedBy("..web..")
    .layer("Domain").definedBy("..domain..")
    .layer("Persistence").definedBy("..persistence..")
    .whereLayer("Web").mayNotBeAccessedByAnyLayer()
    .whereLayer("Domain").mayOnlyBeAccessedByLayers("Web", "Persistence")
    .whereLayer("Persistence").mayNotBeAccessedByAnyLayer();

@ArchTest
static final ArchRule noCycles =
    slices().matching("com.shop.(*)..").should().beFreeOfCycles();
ArchUnit (Java) : une règle de couches et une vérification de cycles écrites sous forme de tests.
// .dependency-cruiser.js
module.exports = {
  forbidden: [
    { name: "no-circular", severity: "error",
      from: {}, to: { circular: true } },
    { name: "domain-not-to-db", severity: "error",
      comment: "Domain code must use repository interfaces, not the database",
      from: { path: "^src/domain" },
      to: { path: "^src/db" } },
  ],
};
dependency-cruiser (JavaScript et TypeScript) : interdire les cycles et empêcher le domaine d’importer la couche base de données.
# .importlinter
[importlinter]
root_package = shop

[importlinter:contract:layers]
name = Web above domain above adapters
type = layers
layers =
    shop.web
    shop.domain
    shop.adapters

[importlinter:contract:features-independent]
name = Feature packages do not import each other
type = independence
modules =
    shop.catalogue
    shop.ordering
    shop.payments
import-linter (Python) : un contrat de couches et un contrat d’indépendance, vérifiés en exécutant lint-imports dans le pipeline.

Remarquez que la règle ArchUnit ci-dessus permet à Persistence d’accéder à Domain, mais pas l’inverse : c’est la disposition centrée sur le domaine qui vient d’être décrite, et non l’organisation en couches classique. Des règles comme celles-ci coûtent peu à ajouter, s’exécutent en quelques secondes et transforment un schéma d’architecture en quelque chose que le build défend. Quand une base de code existante enfreint déjà une règle, la plupart des outils peuvent enregistrer les infractions actuelles comme référence et n’échouer que sur les nouvelles, si bien que la règle empêche la situation de s’aggraver dès le jour où elle est introduite.

Guide de décision

Guide de décision : que faire d’une dépendance indésirable ?

Utilisez ce guide quand une vérification de dépendances échoue ou qu’une revue découvre une dépendance qui pointe dans le mauvais sens.

  1. La dépendance fait-elle partie d’un cycle entre composants ?
    OuiCassez d’abord le cycle (ADP) : poursuivez ci-dessous pour choisir comment.
    NonPassez à la question de la stabilité.
  2. Un côté n’a-t-il besoin que d’un service petit et bien défini de la part de l’autre ?
    OuiInversez la dépendance : le côté qui utilise le service possède une interface, et l’autre côté l’implémente.
    NonPassez à la question suivante.
  3. Les deux côtés dépendent-ils d’un modèle, d’un contrat ou d’un élément de logique partagé ?
    OuiExtrayez-le dans un nouveau composant dont les deux dépendent, et faites en sorte que ce composant soit petit, stable et surtout abstrait.
    NonDemandez-vous si les deux composants n’en forment pas réellement un seul : fusionnez-les, puis redécoupez-les selon une meilleure frontière (section 3.1).
  4. Un composant stable (I faible) dépend-il d’un composant volatil (I élevé) ?
    OuiCela enfreint le SDP. Placez une interface dans le composant stable, ou dans un nouveau composant stable, et déplacez le code volatil derrière elle.
    NonLe sens est acceptable. Inscrivez la règle dans une fonction d’aptitude pour qu’il le reste.

Comment les professionnels gèrent les dépendances

Les architectes expérimentés gardent une image du graphe des dépendances entre composants et la consultent régulièrement, en la générant à partir du code plutôt qu’en la dessinant à la main. Ils décident quels composants sont censés être stables (concepts du domaine, contrats partagés) et lesquels sont volatils (interfaces utilisateur, intégrations), et font pointer les flèches du volatil vers le stable. Ils traitent les nouveaux cycles comme des échecs du build, et non comme des questions de style. Et ils appliquent le même raisonnement entre services : les bibliothèques partagées et les schémas d’événements partagés sont les composants stables d’un système distribué, qui méritent des surfaces minimales et un versionnement soigné, tandis que l’intérieur de chaque service reste libre de changer.

Rien de tout cela n’exige de calculer les métriques avec précision. Leur valeur tient aux questions qu’elles obligent à poser : dans quel sens cette flèche doit-elle pointer, qu’arrive-t-il à tout ce qui est en amont si ceci change, et ce composant est-il aussi abstrait que l’exige le nombre d’éléments qui dépendent de lui ? La section 3.5 poursuit avec les principes SOLID, qui appliquent les mêmes idées à l’intérieur des composants et entre eux.

Vérifiez votre compréhension

Pourquoi tous les composants d’un cycle de dépendances doivent-ils être mis en production ensemble ?

Chacun dépend, directement ou indirectement, de tous les autres : un changement dans l’un peut donc exiger des changements dans les autres, et aucun ne peut être construit ou testé sans les autres.

Un composant a Ca = 3 et Ce = 9. Quelle est son instabilité, et que signifie-t-elle ?

I = 9 / 12 = 0,75. Il dépend de bien plus d’éléments qu’il n’en a qui dépendent de lui : il est donc relativement instable, facile à changer, mais exposé aux changements de ses dépendances.

Qu’est-ce qui se trouve dans la zone de douleur, et quand cela fait-il vraiment mal ?

Les composants à la fois stables (beaucoup de dépendants) et concrets (peu d’abstractions). Cela fait mal quand un tel composant doit changer souvent ; un composant concret qui ne change jamais n’y fait guère de tort.

Quelle est la différence entre organisation en couches stricte et souple ?

Dans l’organisation stricte, une couche ne peut utiliser que la couche juste en dessous d’elle ; dans l’organisation souple, elle peut utiliser n’importe quelle couche inférieure.

Suivant · 3.5SOLID à grande échelleLes cinq principes SOLID expliqués avec précision, chacun illustré en Python puis à l’échelle des composants et des services, ainsi que les principes de cohésion REP, CCP et CRP, leur triangle de tensions et la critique honnête de SOLID.Continuer →

Sources et lectures complémentaires

Une question sur cette section ? ArchitectBot, l’assistant d’étude IA du cours, répond d’abord à partir du cours.

Demander : « Comment trouver et casser les cycles de dépendances dans une grande base de code sans interrompre le développement des fonctionnalités ? »