Anatomie, cycle de vie, variantes et décisions — du monolithe naïf au monolithe modulaire, selon Martin Fowler et Robert C. Martin.
🏛️
Martin Fowler & Uncle Bob — le consensus
« Ne commencez jamais avec les microservices. Un monolithe bien structuré est le bon point de départ — vous ne connaissez pas encore vos frontières de domaine. » — Martin Fowler
Uncle Bob ajoute : « Le monolithe n’est pas le problème. Le monolithe non-modulaire, lui, l’est. Un déploiement unique n’interdit pas une architecture propre. »
01
La métaphore : le couteau suisse
Imagine un couteau suisse. Tous les outils sont dans le même boîtier : lame, tire-bouchon, ciseaux, tournevis. Quand il est bien conçu, chaque outil a sa place et fonctionne indépendamment. Quand il est mal conçu, les outils se bloquent mutuellement et ouvrir le tire-bouchon entraîne la lame.
C’est exactement un monolithe. Le problème n’est pas le boîtier unique — c’est l’organisation interne. Un monolithe modulaire est un couteau suisse bien conçu. Un Big Ball of Mud est un couteau suisse où tout est soudé.
La métaphore va plus loin : si un jour vous avez besoin d’une trononneuse, le couteau suisse ne suffira plus. Mais pour 90% des usages quotidiens, il est parfait — et surtout, il tient dans votre poche.
02
Anatomie d’un monolithe — les 4 couches
Un monolithe est un système déployé comme un seul artefact, exécuté dans un seul processus, avec une seule base de données. Quelle que soit sa taille, il s’organise généralement en 4 couches horizontales.
monolith-app.jar — 1 seul artefact · 1 seul processus · 1 seul déploiement
Une seule base de données partagée par tous les modules. Transactions ACID natives.
03
Les 3 variantes du monolithe
🍰
Variante 01
Monolithe en couches
N-Tier / Layered Architecture. Organisation horizontale classique : Présentation → Logique Métier → Accès aux Données → Base de Données. Chaque couche ne communique qu’avec la couche immédiatement inférieure. Simple, éprouvé, mais les dépendances pointent vers le bas — la DB dicte souvent la structure.
3-TierMVCSéparation of Concerns
🌿
Variante 02
Monolithe modulaire
Modular Monolith — la voie recommandée. Un seul déploiement, mais une structure interne fortement modulaire avec frontières claires entre modules. Chaque module expose une façade publique et protège ses détails internes. Le meilleur des deux mondes : simplicité opérationnelle + séparation des préoccupations.
Modules isolésFaçade publiqueRecommandé
💥
Variante 03
Big Ball of Mud
Anti-pattern — la dégénérescence naturelle. Aucune frontière interne, couplage total, chaque fichier peut importer n’importe quel autre. Résultat inévitable si aucune discipline architecturale n’est maintenue. Le monolithe qu’on critique injustement est souvent celui-ci.
Anti-patternCouplage totalSpaghetti
04
Exemple concret — e-commerce NestJS
Voici la structure typique d’un monolithe NestJS pour un e-commerce. Tous les modules vivent dans le même projet, le même processus, le même déploiement. Les appels entre modules sont des appels de méthode directe — pas de HTTP, pas de gRPC, pas de message broker.
L’avantage clé du monolithe : les appels entre modules sont des appels in-process directs. OrdersService appelle ProductsService.findById() directement en mémoire, sans sérialisation, sans latence réseau, sans gestion d’erreurs distribuées.
@Injectable()
export classOrdersService {
constructor(
private readonly repo: OrdersRepository,
private readonly products: ProductsService, // ← appel direct en mémoireprivate readonly payments: PaymentsService, // ← pas de HTTP, pas de latenceprivate readonly customers: CustomersService, // ← transaction ACID gratuite
) {}
asynccreateOrder(dto: CreateOrderDto): Promise<Order> {
// 1. Vérification stock — appel direct, synchrone, in-processconst product = await this.products.findById(dto.productId);
if (!product.isInStock(dto.quantity))
throw newBadRequestException('Stock insuffisant');
// 2. Paiement — même processus, même transaction DB possibleconst payment = await this.payments.charge(dto.amount, dto.cardToken);
// 3. Création commande — tout dans la même transaction ACIDconst order = await this.repo.save({ ...dto, paymentId: payment.id });
await this.products.decrementStock(dto.productId, dto.quantity);
return order; // Si n'importe quoi échoue → rollback automatique
}
}
L’avantage transactionnel. Dans un monolithe, les opérations save(order), decrementStock() et charge() peuvent vivre dans la même transaction ACID. Si le paiement échoue, tout est automatiquement annulé. En microservices, il faudrait implémenter un Saga pattern avec compensation — 10x plus complexe.
05
Le Monolithe Modulaire — la voie vertueuse
Le monolithe modulaire est la variante recommandée par Martin Fowler et Uncle Bob. Le principe : chaque module expose une façade publique (un fichier *Facade.ts) et protège tout le reste dans un dossier internal/. Les autres modules n’ont accès qu’à la façade.
Résultat : les avantages du monolithe (simplicité de déploiement, transactions ACID, performance in-process) avec les avantages de la modularité (couplage faible, équipes autonomes, refactoring isolé). Et le jour où un module doit devenir un service indépendant, la frontière existe déjà.
Orders
PUBLIC api/
OrdersFacade.ts ✓
PRIVÉ internal/
OrderService.ts
OrderRepository.ts
Order.ts (entity)
Products
PUBLIC api/
ProductsFacade.ts ✓
PRIVÉ internal/
ProductService.ts
ProductRepository.ts
Product.ts (entity)
Payments
PUBLIC api/
PaymentsFacade.ts ✓
PRIVÉ internal/
StripeAdapter.ts
PaymentRepository.ts
Customers
PUBLIC api/
CustomersFacade.ts ✓
PRIVÉ internal/
CustomerService.ts
Customer.ts (entity)
Règle d’or du monolithe modulaire : Un module ne peut importer QUE la façade publique d’un autre module. Jamais un fichier de internal/. Jamais une Entity directement. La façade est le seul point de contact entre modules.
modules/orders/orders.service.ts — via façades
// CORRECT : on passe par les façades publiques des autres modules@Injectable()
export classOrdersService {
constructor(
private readonly repo: OrdersRepository, // ← internal OKprivate readonly products: ProductsFacade, // ← façade publique ✓private readonly payments: PaymentsFacade, // ← façade publique ✓
) {}
}
// INTERDIT : importation depuis internal/ d'un autre module// import { ProductRepository } from '../products/internal/product.repository'; // ✗// import { Product } from '../products/internal/product.entity'; // ✗
06
Le cycle de vie d’un monolithe
Tout monolithe traverse les mêmes phases. Les reconnaître tôt permet d’agir avant la dégénérescence. La clé : modulariser en phase 2, pas en phase 3.
Phase 1 — Naissance (0 à 18 mois)
Le monolithe idéal
Équipe petite (2–5 devs), domaine encore flou, itérations rapides. Le monolithe est le choix optimal : déploiement simple, débogage facile, transactions ACID gratuites. Les modules sont encore petits et bien séparés.
2–5 devsItérations rapidesSimplicité maximale
Phase 2 — Croissance (18 mois à 3 ans)
Les tensions apparaissent
L’équipe grandit (8–15 devs), les modules commencent à s’entremêler. Les builds ralentissent, les conflits de merge se multiplient. C’est le moment critique : modulariser en interne ou subir la dégradation.
8–15 devsBuilds lentsConflits merge
Phase 3 — Maturité douloureuse (3 ans+)
La Big Ball of Mud
Sans discipline, le monolithe dégénère. Chaque fichier peut importer n’importe quel autre, les tests sont lents ou inexistants, déployer est un événement stressant. L’équipe passe plus de temps à contourner le code qu’à le construire.
Couplage totalTests impossiblePeur du déploiement
Phase 4 — La décision
Trois chemins possibles
1. Modulariser le monolithe (monolithe modulaire). 2. Extraire progressivement des services (Strangler Fig). 3. Rester en monolithe si l’équipe et le domaine le permettent. Le pire choix : ne rien faire.
ModulariserStrangler FigRester monolithe
07
Avantages & inconvénients à grande échelle
✓ Forces
Simplicité de déploiement — un seul artefact à déployer, un seul pipeline CI/CD
Transactions ACID natives — pas de saga, pas d’eventual consistency
Débogage simple — un seul processus, stack trace complète, pas de tracing distribué
Performance in-process — appels directs en mémoire, latence nulle entre modules
Onboarding rapide — un seul repo, un seul langage, une seule stack
Coût opérationnel faible — pas de service mesh, pas d’orchestrateur de conteneurs
Refactoring global facile — renommer une méthode se propage dans tout le projet
❌ Limites
Scalabilité monolithique — on scale tout ou rien, impossible de scaler un module seul
Temps de build croissant — le build ralentit avec chaque ligne de code ajoutée
Couplage progressif — sans discipline, les modules finissent entremêlés
Déploiement risqué — un bug dans un module bloque le déploiement de tous les autres
Lock-in technologique — un seul langage, un seul framework, une seule version
Taille d’équipe limitée — au-delà de 8–10 développeurs, les conflits de merge explosent
Redémarrage complet — chaque modification nécessite un redéploiement de tout le système
08
La critique d’Uncle Bob — l’Entity piège
La plus grande erreur dans un monolithe classique : confondre l’Entity ORM (modèle de persistance) avec l’Entity domaine (règles métier). Uncle Bob le répète : « Si votre Entity a un décorateur @Entity ou un import TypeORM, ce n’est pas une Entity au sens de la Clean Architecture. »
Dans un monolithe, cette confusion est encore plus insidieuse car tout vit dans le même processus. La tentation de réutiliser le modèle ORM comme modèle métier est forte — et c’est exactement ainsi que le couplage s’installe progressivement.
Modèle ORM séparé pour TypeORM dans le module infrastructure
Un Mapper traduit entre les deux mondes
new Order(items) suffit pour tester — zero infra
Le péché cardinal : importer directement une Entity TypeORM dans un service métier. Le jour où le schéma DB change, toute la logique métier casse. Séparez vos modèles et utilisez un Mapper.
09
Quand choisir le monolithe ?
Le choix d’une architecture est une décision économique, pas technologique. Martin Fowler le répète : commencez par un monolithe, extrayez des services quand les contraintes l’exigent — pas avant.
Nouveau projet
→
Monolithe modul.
→
Refacto interne
→
Strangler Fig
→
Micro- services
✅
Recommandé
Choisir le monolithe si…
Équipe petite (< 10 devs) avec un domaine encore flou
MVP ou produit en phase de découverte
Transactions ACID requises entre modules
Budget infra limité — pas de Kubernetes en vue
L’équipe n’a pas l’expérience des systèmes distribués
⚠️
Signaux d’alerte
Envisager la migration si…
Les builds dépassent 15–20 minutes régulièrement
Les équipes se bloquent mutuellement à chaque déploiement
Un module nécessite un scaling 10x mais pas les autres
L’onboarding d’un nouveau développeur dépasse 2 semaines
Les tests end-to-end prennent plus de temps que les tests unitaires
💡
Mythes
Les mythes à démystifier
« Monolithe = mauvais » — Faux. Un monolithe modulaire est une architecture saine.
« Microservices = moderne » — Faux. C’est un choix d’organisation, pas de modernité.
« On ne peut pas scaler un monolithe » — Faux. On scale l’instance, pas le module.
« Il faut tout réécrire » — Faux. Le Strangler Fig permet une migration progressive.
« Les monolithes ne peuvent pas être testés » — Faux. Avec une bonne architecture interne, tout est testable.
10
Le diagnostic — Votre monolithe est-il sain ?
Avant de décider de migrer ou de rester en monolithe, posez le diagnostic. Voici les signaux à observer dans votre code, vos builds et votre équipe.
Signaux positifs — monolithe sain
Tests rapides
La suite de tests unitaires s’exécute en moins de 5 minutes. Les tests d’intégration sont isolés par module.
Déploiement serein
L’équipe déploie plusieurs fois par semaine sans angoisse. Le rollback est rapide et automatisé.
Onboarding rapide
Un nouveau développeur est productif en moins d’une semaine. La structure du projet est claire et documentée.
Changements isolés
Modifier un module ne casse pas les autres. Les tests rouges restent dans le périmètre du changement.
Signaux d’alerte — monolithe en souffrance
Le fichier maudit
Un fichier est modifié dans 80% des pull requests. Tout le monde le touche, personne ne le comprend.
Builds > 30 min
Le build prend plus de 30 minutes. Les développeurs lancent le build et vont prendre un café.
Peur de déployer
Le déploiement est un événement stressant. On ne déploie que le vendredi… non, jamais le vendredi.
Bugs cascade
Un changement dans le module Paiement casse le module Commandes. Le couplage est invisible mais réel.
Chemin de sortie recommandé : Si vous avez 3+ signaux d’alerte, commencez par modulariser votre monolithe (monolithe modulaire). Si le monolithe modulaire ne suffit pas, utilisez le pattern Strangler Fig pour extraire progressivement des services — jamais de big-bang rewrite.