Monolithe vs architecture modulaire pour les projets web modernes
Insights/ Architecture web & plateformes / Décisions d'architecture
08 nov. 2023 - 10 min de lecture

Ce que « monolithe » et « modulaire » veulent vraiment dire
Les mots sont utilisés de façon si lâche que deux personnes en train d’avoir un débat « monolithe vs modulaire » discutent souvent de choses différentes. Avant les compromis, une définition propre.
Un monolithe, c’est une seule unité déployable. Un seul codebase, un seul processus, une seule base de données, une seule release. Le système entier sort en une fois et revient en une fois. À l’intérieur de cette unité, le code peut être bien organisé ou un sac de nœuds, mais opérationnellement il se livre comme une seule chose.
Une architecture modulaire, au sens qui compte ici, c’est plusieurs unités déployables, chacune avec son propre code, son propre processus, ses propres données, et son propre rythme de release. Les services se parlent entre eux via des APIs ou des files de messages. Le système entier est le résultat de plusieurs choses qui tournent et coopèrent.
Entre les deux, le monolithe modulaire : une seule unité déployable, mais des modules internes avec des frontières strictes, une infrastructure partagée pour un certain nombre de choses et des APIs explicites entre modules pour un autre. Il se livre comme un monolithe. Il se raisonne comme un système modulaire.
La plupart des débats « monolithe vs modulaire » sont en fait des débats sur celle de ces trois formes qu’un projet précis devrait prendre, et à quel moment il devrait passer de l’une à l’autre. Le contexte pilier de cette décision est dans l’article sur le choix d’architecture ; cet article est le compromis ciblé entre les deux extrêmes et le terrain du milieu.
Ce qu’un monolithe achète vraiment (et ce qu’il coûte)
Un monolithe est le bon point de départ pour presque tout projet web, et reste la bonne réponse pour beaucoup d’entre eux indéfiniment. Cinq choses le rendent peu coûteux.
La simplicité opérationnelle. Un seul processus à déployer, un seul jeu de logs à lire, un seul runtime à surveiller. L’histoire d’astreinte se résume à « le monolithe est-il debout ». Ce n’est pas une petite chose.
Le raisonnement local. Un changement dans une fonction se trace dans le codebase en lisant du code, sans contrat d’API au milieu, sans mode de panne réseau, sans incompatibilité de versions. La plupart des bugs deviennent traitables d’une manière que les systèmes distribués rendent difficile.
Les transactions. Une seule base de données veut dire qu’on peut mettre à jour trois tables dans une transaction sans saga, sans cohérence à terme, sans action compensatoire. Beaucoup de problèmes métier réels sont plus simples quand cela fonctionne.
Le coût de déploiement. Un seul pipeline, un seul jeu d’identifiants, une seule image, un seul rollback. Les systèmes multi-déployables multiplient ce travail, et le multiplicateur est réel.
La liberté de refactoring. Déplacer du code entre modules à l’intérieur d’un monolithe est un refactoring. Déplacer du code entre services à travers le réseau est un projet. Les architectures évoluent, et le coût de déplacer les choses fait partie du coût de l’architecture.
Le prix qu’un monolithe paie est aussi prévisible. Le rayon d’impact : un mauvais changement peut faire tomber tout le système d’un coup. Les frontières d’équipe : quand plus de deux ou trois équipes livrent dans le même codebase, le coût de coordination grimpe plus vite que les effectifs. Le passage à l’échelle : faire passer à l’échelle tout le monolithe parce qu’un sous-domaine est saturé est du gaspillage. Le couplage interne : sans discipline, des modules qui devraient être séparés grossissent silencieusement des dépendances partagées qui rendent toute séparation ultérieure douloureuse.
Aucun de ces points n’est rédhibitoire pour la plupart des projets. C’est le plafond. L’erreur, c’est de traiter le plafond comme s’il était le plancher.
Ce qu’un découpage modulaire achète vraiment (et ce qu’il coûte)
Une architecture modulaire (plusieurs services, chacun avec ses propres données et son propre rythme de release) est la bonne réponse quand la forme du projet le justifie réellement. Quatre choses la rendent précieuse.
Le déploiement indépendant. Une équipe peut livrer un changement sur son service sans se coordonner avec trois autres équipes. À un certain nombre d’équipes, c’est la différence entre un système qui livre chaque semaine et un système qui livre chaque trimestre.
Un rayon d’impact borné. Une mauvaise release dans un service dégrade ce service, pas tout le système. Pour les systèmes où l’uptime par sous-domaine compte, c’est un vrai gain de fiabilité.
Un passage à l’échelle ciblé. Le service chaud passe à l’échelle indépendamment des froids. Pour des charges où un sous-domaine domine la facture, c’est le moyen le moins cher de garder l’hébergement sous contrôle.
La propriété par équipe. Chaque service a une équipe, l’équipe possède le service de bout en bout, et les frontières correspondent à l’organigramme. La loi de Conway joue pour vous au lieu de contre vous.
Le prix est aussi réel. La taxe opérationnelle : chaque déployable est une chose de plus à surveiller, sécuriser, déployer, versionner, et pour laquelle réveiller quelqu’un. Les modes de panne distribués : partitions réseau, pannes partielles, retries, idempotence, cohérence à terme. Aucun ne disparaît simplement parce que l’équipe est compétente. La fragmentation des données : chaque service possède ses données, ce qui veut dire que les requêtes, jointures et rapports inter-services deviennent un problème d’ingénierie séparé. La charge cognitive : un développeur qui arrive sur un système à 12 services passe des semaines sur le système lui-même avant d’être productif sur le travail.
L’erreur ici, c’est l’inverse de l’erreur du monolithe : supposer que le plafond d’un monolithe est plus proche qu’il ne l’est, et payer la taxe du système distribué dès le premier jour pour des bénéfices qui ne se matérialiseront pas avant des années.
Le monolithe modulaire : le terrain du milieu où la plupart des équipes devraient rester plus longtemps
Un monolithe modulaire se livre comme une seule unité déployable mais est structuré comme s’il était modulaire. Les modules internes ont des APIs explicites entre eux. La base de données a des schémas clairs par module, avec la discipline qu’un module ne lit pas directement les tables d’un autre. Le codebase est organisé pour qu’en principe, n’importe quel module puisse être extrait dans un service séparé plus tard.
C’est le terrain du milieu où la plupart des équipes en croissance devraient rester beaucoup plus longtemps qu’elles ne le font. Cela achète la majeure partie de la simplicité opérationnelle d’un monolithe, la majeure partie de la clarté de raisonnement d’un système modulaire, et la quasi-totalité de l’optionalité de l’une ou l’autre direction future. Le coût, c’est la discipline : sans application (linters, frontières de packages, revues de code qui attrapent les violations de frontières), un monolithe modulaire dégénère silencieusement en monolithe enchevêtré, et l’optionalité s’évapore.
Le signal qu’un monolithe modulaire fonctionne, c’est que, le jour venu d’extraire un module dans un service, l’extraction est un projet de l’ordre de quelques semaines, pas un projet de l’ordre de quelques années.
« On le découpera plus tard » et le coût caché du faire-semblant
L’expression « on le découpera plus tard » est la phrase la plus chère dans beaucoup de décisions d’architecture. Dite tôt, elle permet à l’équipe de construire un monolithe enchevêtré avec la croyance rassurante que le nettoyage est un problème pour la-future-elle. Dite plus tard, il s’avère que le découpage est bien plus cher que ce que l’équipe estimait, parce que trois choses se sont passées en silence.
La base de données partagée est devenue un couplage serré. Plusieurs modules lisent et écrivent dans les mêmes tables. Tout découpage doit d’abord démêler les données, ce qui est en soi un projet de plusieurs trimestres, impliquant souvent des migrations en direct sur de vraies données clients.
Les appels internes sont devenus des contrats implicites. Des fonctions d’un module appellent des fonctions d’un autre avec des formes de paramètres que personne n’a documentées. Remplacer ces appels par des APIs réseau exige de rétro-ingéniérer les contrats avant de pouvoir les imposer.
Les attentes opérationnelles se sont durcies autour de l’unique déployable. Les déploiements, le monitoring, les rotations d’astreinte, les runbooks supposent tous un seul processus. Découper veut dire reconstruire tout cela pour un monde multi-services, souvent pendant que l’équipe qui les a écrits est passée à autre chose.
La discipline qui rend « on le découpera plus tard » honnête, c’est d’imposer les frontières de modules dès le début, même quand le système se livre en une seule unité. La discipline qui transforme « on le découpera plus tard » en forme lente d’échec, c’est de sauter cette mise en place et de faire confiance à la-future-elle pour s’en sortir.
Les signaux qui justifient réellement le découpage
Trois signaux, considérés ensemble, indiquent que le découpage modulaire est désormais le bon choix plutôt qu’une envie à la mode.
Le nombre d’équipes a dépassé le plafond de coordination. Quand plus de deux ou trois équipes se bloquent régulièrement entre elles sur le même codebase, le coût de coordination a dépassé le coût de la distribution. C’est généralement entre cinq et huit équipes d’ingénierie, selon le domaine.
Les incidents de rayon d’impact sont devenus récurrents. Un schéma d’incidents où une release dans une partie du système fait tomber autre chose sans rapport est un signal structurel. Cela ne se règle pas par plus de tests seuls.
Un sous-domaine a des besoins de passage à l’échelle fondamentalement différents. Un workflow qui traite cent millions d’événements pendant que le reste du système en traite cent mille a gagné son propre service, peu importe le nombre d’équipes. Le coût de faire passer à l’échelle tout le monolithe pour un seul sous-domaine chaud devient l’argument le moins cher pour le découpage.
Quand moins de deux de ces signaux sont présents, le coût du découpage dépasse presque toujours le coût de rester. Quand les trois sont présents, la question n’est plus s’il faut découper mais comment séquencer l’extraction pour que le système continue à livrer pendant qu’elle a lieu.
Conclusion
Monolithe versus modulaire n’est pas une guerre de religion. C’est une question de l’endroit où le projet se trouve aujourd’hui, où il va, et de la complexité que l’équipe peut absorber sans perdre la capacité à livrer. La plupart des projets commencent comme des monolithes parce que les monolithes sont peu coûteux. Beaucoup d’entre eux restent des monolithes, raisonnablement, pendant des années. Certains deviennent des monolithes modulaires, puis des services sélectivement découpés, dans cet ordre. L’erreur, c’est de choisir l’architecture qui correspond à une équipe future hypothétique et à une échelle future hypothétique, et de la payer en temps réel.
Le contexte plus large, dont la manière dont ce compromis se relie au reste d’une décision d’architecture web, est rassemblé dans le cluster architecture web et plateformes. Et lorsque la question passe de « devrions-nous découper cela » à « nous avons décidé que oui, et il nous faut maintenant quelqu’un pour concevoir les frontières, les APIs et le chemin de migration », c’est exactement ce sur quoi mon accompagnement développement d’API et intégration de systèmes est centré.
- Haja Faniry
Services liés
Développement d'applications web
Développement d'applications web sur mesure pour entreprises, startups et organisations internationales.
Développement d’API & Intégration de Systèmes
Développement d’API et intégration de systèmes permettant de connecter les plateformes numériques et d’automatiser les échanges de données.


