Article

Monolithe modulaire : bien découper une application sans multiplier les services

Une application peut se déployer en un bloc et garder des modules distincts. Exemple avec l’inscription et la facturation, leurs données et leurs échanges.

Une modification de la facturation oblige à retoucher l'inscription, les commandes et plusieurs écrans. Le problème vient peut-être des dépendances entre ces parties, avant même de concerner le déploiement. Un monolithe modulaire cherche à les séparer clairement tout en conservant une application livrée comme un ensemble.

Séparer le découpage du code du mode de déploiement

Le mot monolithe décrit ici une application déployée comme une unité. Il ne dit pas, à lui seul, si son code est bien organisé. On peut avoir des modules nets dans un même déploiement, comme des dépendances très fortes entre des services déployés séparément.

Trois axes indépendants : l'architecture fonctionnelle (découpage en domaines DDD), l'implémentation (CQRS), et le déploiement (un seul processus pour le monolithe, plusieurs pour le distribué).
Trois décisions indépendantes. Le mot « monolithe » ne parle que de la troisième : le déploiement.

Trois décisions méritent donc d'être examinées séparément : quelles parties du métier isoler, comment organiser leur code et comment les déployer. CQRS peut aider à séparer commandes et lectures dans certains modules ; il n'est pas requis pour rendre un monolithe modulaire.

Découper selon des responsabilités compréhensibles

Prenons l'inscription et la facturation. À l'inscription, un client possède une identité, des moyens de connexion et des consentements. À la facturation, il possède une adresse de facturation, un numéro de TVA et un historique de règlements.

Les deux parties parlent de « client », mais n'ont pas besoin du même modèle. Les réunir dans une classe unique peut obliger chaque équipe à comprendre des règles qui ne la concernent pas.

Le DDD, ou conception guidée par le métier, appelle contexte délimité une zone dans laquelle un modèle et son vocabulaire ont un sens précis. Cette notion aide à trouver les frontières. Un contexte peut correspondre à un module ou en regrouper plusieurs : ce n'est pas une correspondance mécanique.

Un module regroupe un besoin de bout en bout

Des dossiers « contrôleurs », « services » et « accès aux données » organisent le code par rôle technique. Ils peuvent être utiles, mais une même fonctionnalité les traverse tous.

Un module de facturation regroupe au contraire les règles, les accès aux données et les points d'entrée nécessaires à la facturation. Il peut lui-même être organisé en couches. Les deux formes de découpage se complètent.

La frontière devient utile quand le reste de l'application passe par une interface définie, plutôt que par les classes internes ou les tables du module.

Définir les échanges et la propriété des données

Trois formes d'échange donnent un vocabulaire simple :

  • Une commande demande une action : « émettre cette facture ».
  • Une requête demande une information : « quel est son statut ? ».
  • Un événement annonce un fait : « la facture a été émise ».

Dans un même processus, ces échanges peuvent utiliser des interfaces locales. Un bus interne est une option pour acheminer les messages ; il n'est pas indispensable dans tous les cas. Le sens des dépendances reste à définir explicitement.

Le module de facturation possède ses données et les règles qui les modifient. Un autre module lui demande une opération ou une information, au lieu d'écrire directement dans ses tables.

Il peut aussi conserver une copie limitée des informations dont il a besoin. Cette copie réduit certaines dépendances, mais exige une mise à jour et une gestion du retard. Elle se choisit pour un usage précis.

Faire vérifier les frontières par les outils

Une convention écrite ne suffit pas toujours. La visibilité des types, les dépendances autorisées entre projets et les tests d'architecture peuvent empêcher un module d'importer des classes internes d'un autre.

Lorsque des éléments sont partagés, leur rôle doit rester clair. Un identifiant ou un petit contrat commun peut être pertinent. Un modèle métier commun qui grossit au gré des besoins risque de réunir de nouveau les responsabilités que l'on voulait séparer.

Un contrôle utile consiste à regarder une modification récente : quelles parties ont dû changer et pourquoi ? Des changements simultanés fréquents peuvent signaler une frontière mal placée ou une interface trop liée aux détails internes.

Garder les bénéfices et les limites du déploiement commun

Un déploiement unique simplifie souvent le lancement local, les livraisons et le suivi des erreurs. Plusieurs instances de l'application peuvent servir davantage de trafic, si son fonctionnement et son stockage s'y prêtent.

En contrepartie, les modules partagent des ressources et un cycle de livraison. Un traitement coûteux peut affecter ses voisins. Multiplier les instances réplique aussi les parties qui n'en avaient pas besoin.

Ces limites peuvent justifier plus tard l'extraction d'un service. Une frontière déjà claire facilite le travail, mais le passage au réseau ajoute toujours des délais, des échecs partiels et des reprises à traiter.

Pour commencer, choisissez une responsabilité bien identifiée, attribuez-lui ses données et faites passer ses consommateurs par une interface. Vous pourrez ensuite mesurer si ce découpage rend les modifications plus locales.

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