Article

Dépendances entre modules : quatre organisations et leurs limites

Appel direct, observation, médiateur ou contrat partagé : comment relier deux modules, choisir la responsabilité de chacun et repérer les dépendances en boucle.

La facturation et la messagerie participent au même parcours client. Faut-il que la facturation demande l'envoi, que la messagerie réagisse à une facture créée, ou qu'un troisième module coordonne les deux ? Ces organisations ne placent pas les mêmes connaissances au même endroit. Pour choisir, regardez quelle partie doit pouvoir changer et quel module est responsable de l'échange.

Un module « connaît » un autre module lorsqu'il utilise son interface, interroge ses données ou comprend les événements qu'il publie. La dépendance va du module qui utilise cette connaissance vers celui qui la fournit.

Nous appellerons les deux modules A et B pour retrouver les mêmes repères dans les schémas. Un éventuel troisième élément sera C. Selon les cas, C sera du code qui coordonne ou un contrat que les deux modules respectent : ce rôle change le sens des flèches.

A appelle B : une liaison directe quand le contrat convient

A a besoin d'une capacité fournie par B et l'appelle. Par exemple, un traitement de facturation demande à un service de calcul le montant de la taxe applicable. A connaît l'opération disponible et les données à lui transmettre ; B n'a pas besoin de savoir qui l'appelle.

Structure 1 : A connaît B
Structure 1, A connaît B : A est le cas d'usage, B la capacité appelée.

Cette organisation est souvent suffisante. Elle devient gênante si l'interface de B change fréquemment ou expose des détails que A devrait pouvoir ignorer. Si remplacer une bibliothèque technique oblige à réécrire les règles métier, la liaison transporte trop de connaissances.

La stabilité utile porte donc sur le contrat. B peut changer son implémentation sans gêner A, à condition de continuer à respecter ce contrat. Je garderais l'appel direct lorsque cette responsabilité est claire, plutôt que d'ajouter une couche uniquement pour embellir le diagramme.

B observe A : la réaction appartient au module abonné

B peut être responsable de réagir à ce que fait A. La messagerie, par exemple, écoute l'annonce d'une facture créée. Elle décide comment préparer le courriel et quel fournisseur utiliser.

Structure 2 : B connaît A
Structure 2, B connaît A : B est le véritable initiateur.

La facturation ignore alors les détails de l'envoi. La messagerie connaît en revanche le contenu et la signification de l'événement. Si ce contrat change, les abonnés peuvent devoir s'adapter.

Cette organisation convient lorsque la réaction peut être traitée séparément. Elle demande aussi de prévoir ce qui se passe si le message est perdu, reçu deux fois ou traité avec retard. Retourner une flèche sans attribuer ces responsabilités ne suffit pas à séparer les modules.

Un médiateur coordonne deux modules

Un troisième module C peut prendre en charge un parcours qui utilise A et B. Pour une commande, il pourrait demander la création de la facture puis déclencher une notification. A et B fournissent leurs capacités ; C connaît l'enchaînement.

Structure 3 : C connaît A et B
Structure 3, C connaît A et B : un médiateur orchestre, A et B s'ignorent.

C'est un médiateur : il porte la relation entre les deux autres. Il peut gérer les étapes, les réponses et la conduite à tenir si l'une des opérations échoue.

Le risque apparaît lorsque tous les parcours finissent dans le même médiateur. Il accumule les règles et les dépendances de toute l'application, si bien qu'une modification locale oblige à comprendre ce composant central. Limitez donc son rôle à un parcours ou à une responsabilité identifiable.

Un contrat partagé permet deux implémentations indépendantes

C peut aussi être une interface ou une définition de message. Il ne coordonne rien : il décrit ce que A et B doivent comprendre de la même façon. A utilise ce contrat, B le réalise ou échange des messages conformes à sa définition.

Structure 4 : A et B connaissent C
Structure 4, A et B connaissent C : ils dépendent d'un contrat partagé.

Pour envoyer une facture, le contrat pourrait préciser le destinataire, le document et le résultat attendu. Le fournisseur d'envoi reste un détail du module qui réalise cette interface.

Le principe d'inversion de dépendance permet notamment de définir l'interface selon les besoins du consommateur. Le code technique s'adapte à cette interface, au lieu d'imposer ses propres choix au métier.

Il faut ensuite décider où le contrat vit et qui peut le faire évoluer. S'il appartient à l'un des modules, ceux qui l'importent dépendent de ce module. S'il est extrait dans une unité commune, les deux dépendent de cette unité. Un contrat fourni par le producteur reste possible ; il ne représente simplement pas la même organisation des dépendances.

Cette question vaut aussi pour les événements. Un bus transporte les messages mais ne décide pas du sens de leurs champs. Hohpe et Woolf décrivent le bus de messages avec les formats et les conventions partagés par ses participants. Sans responsable de ces conventions, chaque équipe risque d'en adopter une interprétation différente.

Comparer les responsabilités avant de choisir

OrganisationResponsabilité de l'échangePoint à vérifier
A appelle BA utilise une capacité de BLe contrat de B reste adapté et suffisamment stable
B observe AB réagit à un fait publié par AB sait traiter les retards, les doublons et les échecs
C coordonne A et BC porte le parcours communC garde un périmètre limité et compréhensible
A et B utilisent un contrat CChacun respecte une interface communeLe contrat a un emplacement, un responsable et des règles d'évolution

Ces formes peuvent coexister dans la même application. Le choix dépend de la relation précise à organiser. Deux domaines métier peuvent même employer le même mot avec des sens différents : comme l'explique la notion de contexte délimité, mieux vaut parfois traduire entre leurs modèles que leur imposer un objet commun.

Une combinaison de liaisons peut former une boucle

A peut utiliser B, puis B commencer à utiliser A pour répondre à un nouveau besoin. La dépendance revient à son point de départ. Le même problème peut passer par un intermédiaire : A utilise B, B utilise C et C utilise A.

Cycle direct : A connaît B et B connaît A
Cycle direct : A et B dépendent l’un de l’autre. Cette relation complique les évolutions indépendantes ; son effet dépend de ce qui est partagé et de la compatibilité des changements.
Cycle indirect : A connaît B, B connaît C, C connaît A
Cycle indirect : la boucle se referme par un tiers, et chaque flèche, isolée, semble innocente.

À l'échelle de modules que vous voulez construire ou livrer indépendamment, cette boucle peut imposer des changements coordonnés. Le principe des dépendances acycliques de Robert C. Martin vise à éviter ce regroupement involontaire.

Les événements rendent parfois ces relations moins visibles dans le code. Martin Fowler souligne cette difficulté : des composants découplés par des notifications peuvent former un parcours global difficile à retrouver. Ajoutez donc au schéma les abonnements et les contrats de messages, pas seulement les appels de méthodes.

Pour relire votre architecture, partez d'un changement concret et suivez les modules qu'il touche. Le schéma sert à expliquer cette propagation. Une liaison directe bien comprise peut être plus facile à maintenir qu'une succession d'intermédiaires dont personne ne sait qui possède la règle.

Sources

Explorer

Sur le même sujet

Choisis dans l’index d’après les étiquettes de cet article — les plus proches d’abord, hors de sa série. Rien n’est écrit à la main : un article publié demain avec les mêmes étiquettes y prendra place.

Laisser un commentaire

Elle n'est jamais affichée ni publiée. Elle sert à regrouper vos commentaires et, si vous vous inscrivez un jour avec elle, à vous les rendre.

Votre commentaire sera relu avant d'être publié.