Be Software Architecture Wise
Be Wise Apps

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

Section 5.8 sur 5.8 · Intermédiaire · 24 min de lecture

Quand un monolithe est la bonne réponse

Leçon vidéo

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

Avant que le chapitre six ne franchisse le réseau, il vaut la peine de se demander directement : quand un monolithe est-il la bonne réponse ? Vous verrez les arguments, jusqu’où un monolithe peut passer à l’échelle, et les preuves qui indiquent qu’il est temps de le découper.

Un nouveau restaurant commence généralement avec une seule cuisine, où chaque cuisinier peut voir et aider tous les autres. Ouvrir cinq cuisines séparées dans toute la ville dès le premier jour, chacune avec son personnel, ses livraisons et son responsable, coûterait bien plus cher, et ferait de chaque changement de la carte une négociation. Ce n’est que lorsque l’unique cuisine est vraiment débordée, ou qu’un plat exige un équipement spécial, qu’en ouvrir une autre devient judicieux. Il en va de même pour le logiciel. Et souvenez-vous : monolithe décrit le déploiement, pas la qualité.

En 2015, Martin Fowler a esquissé le surcoût des microservices. C’est purement indicatif, sans données réelles. En bas, la complexité ; sur le côté, la productivité. Les microservices entraînent des coûts qui leur sont propres : déployer de nombreuses unités, la supervision, les défaillances partielles, les API versionnées. En dessous d’une certaine complexité, c’est un surcoût qui ne rapporte rien. Un monolithe ralentit bel et bien à mesure qu’il grandit. Ce n’est qu’au-delà du croisement que les microservices sont rentables. Fowler a aussi observé que presque tous les systèmes de microservices réussis avaient commencé comme un monolithe devenu trop gros, et que presque tous ceux construits en microservices dès le départ avaient connu de graves difficultés. Il est difficile de trouver de bonnes frontières avant de comprendre le domaine.

David Heinemeier Hansson, créateur de Ruby on Rails, a plaidé pour les petites équipes dans The Majestic Monolith : ne distribuez pas vos traitements, sauf si vous y êtes vraiment obligé. Stefan Tilkov a défendu le point de vue opposé : découper un monolithe après coup est souvent plus difficile que prévu, parce que ses parties deviennent fortement couplées. Les deux positions sont plus proches qu’il n’y paraît. Toutes deux disent que la partie difficile, ce sont les frontières, et un monolithe dont les modules sont imposés dès le premier jour répond à l’essentiel de la préoccupation de Tilkov.

Alors, quand un monolithe est-il le bon choix ? Avec une petite équipe : le coût de communication que suppriment les microservices est faible, mais pas leur coût d’exploitation. Avec un domaine incertain ou un produit à ses débuts. Déplacer une frontière entre modules est une refactorisation ; la déplacer entre services est une migration. Quand vous avez besoin d’une forte cohérence. Une seule transaction de base de données couvrant les commandes et les paiements est simple et correcte dans un monolithe. Quand la capacité d’exploitation est limitée, parce que chaque service a besoin de son propre pipeline, de sa supervision et de connaissances pour l’astreinte. Et quand des parties du domaine échangent beaucoup. Les appels internes au processus prennent de quelques nanosecondes à quelques microsecondes ; les appels réseau prennent des millisecondes, et peuvent échouer. Dans tous les cas, faites-en un monolithe modulaire.

Une croyance répandue veut que les monolithes ne puissent pas passer à l’échelle. Ils vont plus loin que ce dont la plupart des systèmes auront jamais besoin. Exécutez de nombreuses copies identiques derrière un équilibreur de charge, ce qui ne fonctionne que si l’application est sans état. Les sessions et les caches se trouvent donc dans un stockage partagé, et n’importe quelle réplique peut servir n’importe quelle requête. Toutes les écritures vont à la base de données primaire, tandis que les répliques en lecture, avec un léger retard, servent l’historique des commandes et les rapports. Et le travail lent, comme les e-mails et les versements, est placé dans une file. Des processus d’arrière-plan prennent le relais. Ils exécutent le même code avec une commande de démarrage différente, si bien que les rôles web et worker passent à l’échelle séparément.

Vérifions cela avec des chiffres d’illustration. Une start-up de livraison de repas comptant sept ingénieurs prévoit un pic de mille deux cents requêtes par seconde d’ici deux ans. Une réplique en traite cent quatre-vingts en respectant son objectif de temps de réponse. Mille deux cents divisé par cent quatre-vingts donne environ six virgule sept, donc sept répliques. Avec environ cinquante pour cent de marge, de quoi survivre à la défaillance d’une zone de disponibilité, cela fait environ onze au pic. Environ quinze pour cent des requêtes écrivent dans la base de données, et chacune effectue environ trois écritures. Cela fait environ cinq cent quarante écritures par seconde, largement dans les capacités d’un seul primaire PostgreSQL. Un cache pour les cartes absorbe l’essentiel des lectures, et deux répliques en lecture prennent en charge l’historique des commandes et les rapports. Ainsi, une seule base de code, exécutée sous forme d’environ onze répliques web plus des workers, atteint l’objectif. Le budget d’architecture va aux frontières des modules et à l’absence d’état, pas au découpage.

Quand est-il temps de découper ? Quand il y a des preuves qu’une unité de déploiement est la contrainte : les équipes s’attendent les unes les autres, et de petits correctifs patientent des jours derrière des travaux sans rapport. Ou quand un module, comme le suivi des livreurs en temps réel, a besoin de cinquante fois la capacité du reste au moment du pic, ou doit rester disponible quand tout le reste est en panne. Ou quand une partie a des besoins différents, comme des données de carte bancaire qui justifient une unité séparée et étroitement contrôlée. Un code lent n’est pas une raison ; le découpage ne le corrigera pas. Découpez alors de façon incrémentale, un module à la fois, en suivant les contextes délimités, en commençant par celui qui a la frontière la plus claire et la meilleure raison de partir.

Ainsi s’achève le chapitre cinq. La section complète propose un guide de décision, une comparaison de tous les styles du chapitre, et une liste de contrôle pour un monolithe sain. Quand vous serez prêt, faites le test facultatif du chapitre, puis passez au chapitre six, où nous emmenons ces questions à travers un réseau. Bravo.

Ce qu’il faut savoir

La question qui sous-tend le chapitre

Les styles de ce chapitre se déploient tous comme une seule unité. Le chapitre 6 pose les mêmes questions à travers un réseau, où chaque appel peut échouer, être lent ou arriver deux fois. Avant de franchir cette ligne, il vaut la peine de se demander directement : quand un monolithe est-il la bonne réponse ? Pendant une bonne partie des années 2010, la réponse par défaut du secteur était « rarement », et de nombreuses équipes ont découpé de jeunes systèmes en microservices parce que de grandes entreprises célèbres l’avaient fait. Une décennie d’expérience a produit une vision plus équilibrée, que présente cette section.

Le mot monolithe demande de la prudence. Comme l’a expliqué la section 5.1, il décrit le déploiement (une seule unité construite, testée et publiée d’un bloc), pas la qualité interne. Un monolithe peut être un système modulaire bien structuré ou une grosse boule de boue, et il en va de même d’un ensemble de services. Beaucoup des problèmes imputés aux monolithes sont en réalité des problèmes de mauvaise modularité, et découper en services un système mal modularisé produit une grosse boule de boue distribuée, ce qui est pire.

MonolithFirst et le surcoût des microservices

En mai 2015, Martin Fowler a décrit le surcoût des microservices (microservice premium) : les microservices entraînent des coûts réels qui leur sont propres (déploiement automatisé de nombreuses unités, supervision, gestion des défaillances partielles, cohérence à terme, API versionnées entre équipes), et ces coûts ralentissent une équipe, sauf si le système est assez complexe pour que les microservices les remboursent. Son conseil était de partir par défaut d’un monolithe unique et bien modularisé, et de ne passer aux microservices que lorsque la complexité l’exige.

Un mois plus tard, dans un article intitulé MonolithFirst (juin 2015), il a ajouté deux observations tirées des projets qu’il avait vus : presque tous les systèmes de microservices réussis avaient commencé comme un monolithe devenu trop gros, puis découpé, et presque tous les systèmes construits en microservices dès le départ avaient fini en grande difficulté. L’une des raisons est qu’il est difficile de trouver de bonnes frontières entre services avant d’avoir compris le domaine, et qu’une mauvaise frontière entre services coûte bien plus cher à déplacer qu’une mauvaise frontière entre modules. Il a décrit plusieurs voies : construire un monolithe et en détacher plus tard des services par les bords ; construire un monolithe délibérément jetable ; ou commencer avec quelques services à gros grain et les découper davantage une fois les frontières stabilisées.

Le surcoût des microservices

Croquis conceptuel du surcoût des microservices : la productivité d’un monolithe chute plus vite avec la complexité, mais celle des microservices part plus bas ; croisement à forte complexité seulementComplexité de base du système et de l’organisationProductivitéMonolitheMicroservicescroisementSurcoût : coût d’exploitation denombreux services, sans contrepartieLe monolithes’alourditIndicatif, d’après le croquis de Martin Fowler (2015) ; pas de données réelles
Un croquis conceptuel : les microservices ne sont rentables qu’au-delà d’un certain niveau de complexité ; en deçà, leur coût d’exploitation est un surcoût qui ne rapporte rien.

Le monolithe majestueux, et le point de vue opposé

En février 2016, David Heinemeier Hansson, créateur de Ruby on Rails, a publié « The Majestic Monolith » sur le blog Signal v. Noise. Il y décrivait Basecamp, alors construit par une équipe d’une douzaine de programmeurs, comme une application intégrée unique servant des clients web, mobiles, de bureau et e-mail, et soutenait que les petites équipes devraient adopter un monolithe bien conçu plutôt que copier l’architecture d’organisations comptant des milliers d’ingénieurs. Sa phrase clé était Ne distribuez pas vos traitements !, sauf si vous y êtes vraiment obligé.

Les praticiens ne sont pas tous d’accord. Également en juin 2015, Stefan Tilkov a soutenu sur le site de Fowler, dans « Don’t start with a monolith », qu’il est souvent plus difficile que prévu de découper un monolithe après coup, parce que ses parties deviennent fortement couplées au travers des bibliothèques partagées, des objets du domaine et de la base de données. Si le système est manifestement assez grand pour justifier des parties séparées et que le domaine est bien compris, il recommandait de concevoir des sous-systèmes séparés dès le départ. Les deux positions sont moins éloignées qu’il n’y paraît : toutes deux disent que la partie difficile, ce sont les frontières, et un monolithe dont les modules sont imposés dès le premier jour (section 5.3) répond à l’essentiel de la préoccupation de Tilkov.

Les cas réels vont dans les deux sens, mais partagent une leçon. Segment a décrit en 2018 comment l’entreprise avait regroupé plus de 140 petits services en un seul service, après que la charge d’exploitation et la divergence des bibliothèques partagées avaient ralenti la livraison ; Amazon Prime Video a fait passer en 2023 une charge de travail de supervision de composants serverless distribués à un processus unique (voir la section 1.3). À l’inverse, des organisations comptant des centaines d’équipes, comme les cas à grande échelle de la section 6.3, tirent des services une réelle indépendance. La leçon est l’adéquation : le style de déploiement doit correspondre à l’organisation, à la maturité du domaine et aux qualités requises.

Quand un monolithe est la bonne réponse

Une petite équipe. Avec une à quelques équipes, le coût de communication que suppriment les microservices est faible, alors que leur coût d’exploitation est le même que pour une grande organisation. Un monolithe permet à chacun de comprendre et de modifier l’ensemble du système.

Un domaine incertain ou un produit à ses débuts. Tant que vous découvrez encore ce qu’est le produit, les frontières vont bouger. Déplacer une frontière entre modules est une refactorisation ; la déplacer entre services est une migration, avec des données, des API et des déploiements.

De forts besoins de cohérence. Une seule transaction de base de données couvrant les commandes, les paiements et les stocks est simple et correcte dans un monolithe. Entre services, il faut des sagas, des outbox et de la compensation (section 6.7).

Une capacité d’exploitation limitée. Chaque service a besoin de son propre pipeline, de sa supervision, de ses alertes, de ses règles de passage à l’échelle et de connaissances pour l’astreinte. Une équipe sans plateforme pour cela passera son temps sur la tuyauterie.

Des interactions sensibles aux performances. Les appels internes au processus prennent de quelques nanosecondes à quelques microsecondes ; les appels réseau prennent des millisecondes et peuvent échouer. Des interactions bavardes entre parties du domaine coûtent bien moins cher à l’intérieur d’un seul processus.

Dans tous ces cas, la forme recommandée est un monolithe modulaire, et non un monolithe non structuré. Il garde ouverte la possibilité d’extraire des services, comme l’explique la section 5.3.

Faire passer un monolithe à l’échelle

Une croyance répandue veut que les monolithes ne puissent pas passer à l’échelle. Ils vont plus loin que ce dont la plupart des systèmes auront jamais besoin, grâce à l’axe X du cube de scalabilité (scale cube) décrit par Martin Abbott et Michael Fisher (exécuter de nombreuses copies identiques), associé à quelques tactiques classiques. Le chapitre 9 traite chacune en profondeur (section 9.1).

Des répliques horizontales derrière un équilibreur de charge. Exécutez de nombreuses copies identiques de l’application et répartissez les requêtes entre elles. Cela ne fonctionne que si l’application est sans état : les sessions, les fichiers envoyés et les caches doivent se trouver dans des stockages partagés, pour que n’importe quelle réplique puisse servir n’importe quelle requête (section 9.2). Cela apporte aussi de la redondance : la défaillance d’une réplique n’arrête plus le service.

Des répliques en lecture. La plupart des systèmes lisent bien plus qu’ils n’écrivent. Les répliques en lecture de la base de données copient les modifications du primaire et servent des lectures comme l’historique des commandes et les rapports, au prix d’un retard de réplication : une réplique peut avoir une fraction de seconde de retard (section 7.4).

La mise en cache. Les cartes, les pages produits et la configuration changent rarement et sont lues en permanence ; les mettre en cache en mémoire ou dans un cache partagé supprime l’essentiel de la charge sur la base de données (section 7.6). Les ressources statiques vont sur un CDN.

Des processus d’arrière-plan. Le travail lent ou irrégulier (e-mails, factures PDF, imports, versements) est placé dans une file et traité par des processus d’arrière-plan (workers). Les workers exécutent généralement la même base de code avec une commande de démarrage différente : le système reste une seule base de code, mais les rôles web et worker passent à l’échelle séparément (section 9.3).

Des machines plus grosses et le partitionnement. La scalabilité verticale de la base de données est souvent l’étape suivante la plus simple. Pour les très grands systèmes, les données peuvent être partitionnées par client (tenant) ou par région, avec une copie complète du monolithe par partition (l’axe Z du cube de scalabilité). Stack Overflow est un exemple bien connu de la distance que peut parcourir une conception largement monolithique : selon le compte rendu de ses propres ingénieurs, un jour de février 2016, ses équilibreurs de charge ont traité environ 209 millions de requêtes HTTP, l’application principale de questions-réponses (une application multi-tenant unique pour tous ses sites de questions-réponses) tournant sur neuf serveurs web principaux, avec quatre machines SQL Server au service de tout le réseau.

Un monolithe déployé à l’échelle

Monolithe à l’échelle : répliques sans état identiques derrière un équilibreur de charge, cache et sessions partagés, base de données primaire avec répliques en lecture, et workers alimentés par une file, tous sur la même base de codeLoad balancerMême build, plusieurs copies (sans état)Réplique 1rôle webRéplique 2rôle webRéplique 3rôle webWorkersmême code, rôle workerCache + sessionsex. RedisBD primaireécrituresRépliques lect.rapports, listesFile tâchese-mails, versem.réplication (en retard)déposeconsommePlus de requêtes : plusde répliques ; plus detâches : plus de workers
Une base de code, de nombreuses copies : des répliques web sans état derrière un équilibreur de charge, un cache et des sessions partagés, une base de données primaire avec des répliques en lecture, et des workers alimentés par une file.

Exemple commenté

Exemple commenté : jusqu’où ira un seul monolithe ?

Une nouvelle start-up de livraison de repas compte 7 ingénieurs et prévoit, d’ici la fin de la deuxième année, un pic de 1200 requêtes HTTP par seconde le vendredi à l’heure du dîner. Un test de charge du monolithe modulaire montre qu’une réplique (4 vCPU) traite 180 requêtes par seconde en respectant l’objectif de 300 ms au p95. Environ 85 % des requêtes sont des lectures (cartes, suivi des commandes). Les chiffres sont fournis à titre d’illustration.

Répliques pour le pic1200 / 180 = 6,7, donc 7 répliques au pic
Marge de capacité (headroom)Garder le taux d’utilisation bien en dessous de la saturation (section 2.4) et survivre à la défaillance d’une zone de disponibilité : prévoir environ 50 % de marge, donc 7 x 1,5 = environ 11 répliques au pic, avec réduction en dehors des pics
Écritures en base de données15 % de 1200 = 180 requêtes par seconde, chacune effectuant environ 3 écritures : environ 540 écritures par seconde, largement dans les capacités d’un seul primaire PostgreSQL managé sur une instance de taille moyenne
Lectures en base de données1020 requêtes de lecture par seconde ; un cache pour les cartes (qui changent rarement) en absorbe l’essentiel, et deux répliques en lecture prennent en charge l’historique des commandes et les rapports, en acceptant environ une seconde de retard de réplication
Travail en arrière-planLes e-mails de reçu, les versements aux livreurs et les rapports pour les restaurants passent à un pool de workers alimenté par une file, pour qu’un fournisseur d’e-mail lent ne puisse pas ralentir l’étape de paiement
ConclusionUne seule base de code, déployée comme une seule unité et exécutée sous forme d’environ 11 répliques web plus des workers, atteint l’objectif à deux ans. Le budget d’architecture va aux frontières des modules et à l’absence d’état, pas au découpage.

La limite atteinte en premier est rarement le CPU. Le plus souvent, c’est la coordination des équipes à mesure que le groupe d’ingénierie grandit, ou un module unique (comme l’affectation) dont les besoins de passage à l’échelle ou de mise en production sont très différents.

Chaque chiffre ici devrait être remplacé par des mesures issues de vos propres tests de charge ; la planification de la capacité est traitée à la section 9.7.

Les signes qu’il est temps de découper

Le découpage doit répondre à des preuves, pas à la mode. Les signes qu’une unité de déploiement est devenue la contrainte sont surtout organisationnels et opérationnels :

La contention entre équipes. Plusieurs équipes modifient le même code et s’attendent les unes les autres ; les conflits de fusion, les réunions de coordination et les builds cassés par d’autres équipes se multiplient à mesure que l’organisation grandit. Si les modules sont déjà bien délimités et que la contention persiste, c’est peut-être l’indépendance de déploiement qui manque.

Les files d’attente de déploiement. Les mises en production sont regroupées en trains parce que chaque modification exige de retester tout le système ; un petit correctif attend des jours derrière des travaux sans rapport ; la modification ratée d’une équipe bloque la mise en production de tout le monde.

Des besoins de passage à l’échelle ou de disponibilité radicalement différents. Un module (par exemple, le suivi des livreurs en temps réel) a besoin de cinquante fois la capacité du reste au moment du pic, ou doit rester disponible quand tout le reste est en panne. Faire passer tout le monolithe à l’échelle pour les besoins d’un seul module devient coûteux.

Un périmètre de sécurité ou de conformité différent. Les données de carte bancaire ou les dossiers médicaux peuvent justifier une unité séparée et étroitement contrôlée, afin de garder le reste du système hors du périmètre d’audit.

Des besoins technologiques différents. Par exemple, un modèle d’apprentissage automatique qu’il vaut mieux servir depuis Python au sein d’un monolithe Java.

Certaines raisons ne sont pas de bonnes raisons à elles seules : « les monolithes, c’est du legacy », l’envie d’utiliser une nouvelle technologie, ou un code lent (qui est un problème de conception que le découpage ne résoudra pas et risque d’aggraver en ajoutant des appels réseau).

Comment découper le moment venu

Découpez de façon incrémentale, un module à la fois, en commençant par celui qui a à la fois la frontière la plus claire et la meilleure raison de partir. Modularisez d’abord si le code n’est pas encore modulaire : extraire un service d’un enchevêtrement entraîne l’enchevêtrement avec lui. Ensuite, extrayez en suivant les contextes délimités (section 5.7) : placez une API réseau devant l’API existante du module, déplacez ses données dans sa propre base de données, remplacez les événements internes au processus par un courtier de messages (message broker) et remplacez toute transaction partagée par des sagas explicites. Dirigez progressivement le trafic vers le nouveau service et conservez l’ancien chemin jusqu’à ce que le nouveau ait fait ses preuves.

C’est l’approche strangler fig (figuier étrangleur) et ses variantes, traitées à la section 11.6. La destination n’est souvent pas « tout en microservices », mais un monolithe avec quelques services extraits là où les preuves l’exigeaient ; l’architecture à base de services de la section 6.2 est un état final courant et pragmatique.

Les styles du chapitre 5 comparés

CaractéristiqueEn couchesMonolithe modulaireTubes et filtresMicronoyauHexagonale / Clean
Partitionnement principalTechniqueDomaineÉtapes de traitementNoyau plus plug-insIntérieur contre extérieur
SimplicitéÉlevéMoyen à élevéÉlevéMoyenBas à moyen
Coût globalBon marchéBon marchéBon marchéBon marchéFaible à moyen
Modifiabilité pour les changements métierBas à moyenÉlevéÉlevée au sein du fluxÉlevée dans les directions prévuesÉlevée pour l’infrastructure
TestabilitéMoyenMoyen à élevéÉlevéMoyen à élevéÉlevé
DéployabilitéBasBas à moyenBas à moyenMoyenDépend du style de déploiement
ScalabilitéBasBas à moyenBas à moyenBasDépend du style de déploiement
Tolérance aux pannesBasBasBas à moyenBas à moyenDépend du style de déploiement
Usage typiqueApplications petites à moyennes, une équipeProduits en croissance, plusieurs équipesETL, médias, journaux, compilateursProduits à nombreuses variantesLogique qui doit survivre aux frameworks

Les évaluations résument les jugements portés dans chaque section et supposent des implémentations typiques. La conception pilotée par le domaine n’apparaît pas comme colonne, car c’est une méthode pour trouver des frontières, que n’importe lequel de ces styles peut utiliser. Les systèmes réels combinent les styles : un monolithe modulaire dont les modules sont hexagonaux à l’intérieur, avec une tâche d’import en tubes et filtres et un micronoyau pour les règles fiscales, est tout à fait normal.

Guide de décision

Guide de décision : choisir parmi les styles du chapitre 5

Utilisez-le pour un nouveau système ou une restructuration majeure qui fonctionnera comme une seule unité de déploiement. Parcourez les questions de haut en bas ; plusieurs styles peuvent s’appliquer à différents niveaux.

  1. Le système est-il principalement un flux de données passant par une série de transformations (importer, nettoyer, enrichir, exporter), plutôt que des utilisateurs qui interagissent avec un état partagé ?
    OuiUtilisez des tubes et filtres pour ce flux (section 5.4). Gardez chaque filtre sans état et le format des données entre filtres explicite.
    NonPassez à la question suivante.
  2. Le produit a-t-il un flux de travail central stable, mais de nombreuses variantes de certaines étapes (par pays, client, fournisseur ou format de fichier), ou des tiers doivent-ils pouvoir l’étendre ?
    OuiUtilisez un micronoyau (section 5.5) pour ces points de variation, idéalement après avoir construit deux ou trois variantes, pour que le contrat repose sur des cas réels.
    NonPassez à la question suivante.
  3. Est-il petit (une équipe, quelques dizaines d’écrans), avec des règles simples, et peu susceptible de beaucoup grandir ?
    OuiUtilisez une architecture en couches (section 5.2) avec des couches fermées imposées par un test d’architecture. N’ajoutez pas plus de structure que le problème n’en demande.
    NonPassez à la question suivante.
  4. Plusieurs équipes y travailleront-elles, ou le domaine couvre-t-il plusieurs domaines ayant chacun leur propre langage (cartes, commandes, affectation) ?
    OuiUtilisez un monolithe modulaire (section 5.3) avec des modules tracés selon les contextes délimités (section 5.7), chacun propriétaire de ses données et de son API.
    NonUne structure en couches ou modulaire convient l’une comme l’autre ; préférez des modules par domaine si une croissance est probable, car restructurer plus tard coûte cher.
  5. À l’intérieur de chaque module ou application, la logique métier est-elle substantielle, ou dépend-elle de services externes susceptibles de changer ?
    OuiStructurez l’intérieur avec des ports et des adaptateurs (section 5.6) : le domaine au centre, des adaptateurs pour le web, la base de données et les fournisseurs.
    NonGardez la structure par défaut du framework à l’intérieur du module ; n’ajoutez des ports qu’aux frontières susceptibles de changer.

Liste de contrôle : un monolithe qui reste sain et découpable

La plupart de ces points coûtent peu s’ils sont faits tôt, et très cher s’ils sont ajoutés après coup.

  • Les modules suivent les domaines métierLes packages de premier niveau portent le nom des contextes délimités (orders, dispatch, payments), pas celui des couches techniques.
  • Chaque module a une petite API publiqueTout le reste est interne ; un test d’architecture fait échouer le build en cas d’import interdit.
  • Chaque table a un seul module propriétaireDes schémas séparés ou des conventions de nommage rendent la propriété visible ; aucun module ne lit les tables d’un autre.
  • L’application est sans étatLes sessions, les fichiers envoyés et les caches se trouvent dans des stockages partagés : n’importe quelle réplique peut servir n’importe quelle requête, et l’on peut ajouter des répliques à volonté.
  • Le travail lent s’exécute dans des processus d’arrière-planLes e-mails, rapports, versements et imports passent par une file : les répliques web restent rapides, et les deux peuvent passer à l’échelle séparément.
  • Le build et les tests restent rapidesSuivez les durées de build et de test ; gardez le pipeline principal sous 10 à 15 minutes environ en testant les modules séparément et en parallèle.
  • Le déploiement est automatisé et fréquentDes mises en production petites et fréquentes avec retour arrière (rollback) automatisé ; les feature flags découplent le déploiement de la mise en production.
  • Observabilité par moduleLes journaux, métriques et traces sont étiquetés avec le module, pour que vous puissiez voir quel module est lent, défaillant ou coûteux.
  • Les signes de tension sont mesurésLe délai de mise en œuvre des changements, la longueur de la file de déploiement, les conflits de fusion par équipe et la consommation de ressources par module sont examinés régulièrement, pour que la décision de découper repose sur des preuves.

Vérifiez votre compréhension

Pourquoi une mauvaise frontière entre services coûte-t-elle plus cher qu’une mauvaise frontière entre modules ?

Déplacer une frontière de module est une refactorisation au sein d’une seule base de code et d’une seule transaction de base de données ; déplacer une frontière de service modifie des API réseau, des stockages de données, des déploiements et souvent la répartition des responsabilités entre équipes, et doit se faire sans interruption de service.

Que doit-il être vrai d’un monolithe avant que vous puissiez le faire passer à l’échelle en ajoutant des répliques ?

Il doit être sans état : les sessions, les fichiers et les caches doivent se trouver dans des stockages partagés, pour que n’importe quelle réplique puisse servir n’importe quelle requête.

Citez deux signes qu’un monolithe est devenu la contrainte, et une raison de découper qui ne suffit pas à elle seule.

Signes : une contention persistante entre équipes et des files d’attente de déploiement, ou un module dont les besoins de passage à l’échelle ou de conformité sont très différents. Insuffisant à lui seul : la mode, l’envie d’une nouvelle technologie, ou un code lent.

En quoi les positions MonolithFirst et « Don’t start with a monolith » se rejoignent-elles ?

Toutes deux disent que le problème difficile est de trouver et de conserver de bonnes frontières ; un monolithe dont les frontières de modules sont imposées répond à la préoccupation de Tilkov tout en conservant le coût plus faible défendu par Fowler.

Fin du chapitre 5Passer le test du chapitreFacultatif : 20 questions à choix multiples et 2 réponses rédigées, dans l’application.Accéder au test →

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 : « Nous sommes cinq développeurs à construire un nouveau produit B2B de planification, et les investisseurs nous interrogent sans cesse sur les microservices. Comment expliquer pourquoi nous commençons par un monolithe modulaire, et ce qui nous ferait changer d’approche ? »