Modular Monolith : L'Architecture Que Tout le Monde Redécouvre

Vertical Slice, domain-first, package by feature - comment une idée ancienne est devenue la réponse moderne à la complexité des microservices.

Par l'équipe Sinra

Le Retour du Monolithe Intentionnel

Il y a quelques années, dire « on construit un monolithe » dans une conférence technique revenait à admettre une défaite. Le monolithe était synonyme de code legacy, de dette technique accumulée, de systèmes impossibles à maintenir.

Aujourd’hui, quelque chose a changé.

Des équipes chez Shopify, Stack Overflow, Basecamp et GitHub parlent ouvertement de leurs monolithes bien construits. Des ingénieurs reconnus publient des articles sur les limites des microservices. Et une architecture particulière émerge comme alternative sérieuse :

Le Modular Monolith avec Vertical Slice Architecture.

Ce n’est pas une régression. C’est une évolution.


Historique : Une Idée Qui a Plusieurs Vies

Les Années 2000 : Le Monolithe Sans Structure

Les premiers frameworks web - Rails, Django, Spring MVC - ont imposé une organisation horizontale : toutes les vues ensemble, tous les modèles ensemble, tous les contrôleurs ensemble.

app/
  controllers/   <- toutes les routes
  models/        <- tous les modèles
  views/         <- tous les templates
  services/      <- toute la logique

Ça fonctionne bien jusqu’à environ 20-30 modèles. Au-delà, la navigation devient un cauchemar. Un changement dans UserService casse OrderService. Les dépendances circulaires prolifèrent. Les nouveaux développeurs ne savent pas par où commencer.

2010-2018 : La Fuite Vers les Microservices

Face à cette complexité, l’industrie a pris une décision radicale : distribuer le problème.

Si un monolithe devient ingérable, découpons-le en services indépendants. Chaque service gère son propre domaine, sa propre base de données, son propre déploiement.

Le problème ? La complexité ne disparaît pas - elle se déplace vers l’infrastructure.

  • Comment les services communiquent-ils ? (REST ? gRPC ? événements ?)
  • Comment gérer les transactions distribuées ?
  • Comment déboguer une requête qui traverse 12 services ?
  • Comment maintenir la cohérence des données entre services ?

Pour les équipes de Netflix, Uber ou Amazon, ce coût se justifie. Pour le reste, c’est souvent un surcoût injustifié.

2014 : Jimmy Bogard et la Vertical Slice Architecture

En 2014, Jimmy Bogard - créateur de AutoMapper et MediatR - formule ce qu’il observe dans les bases de code qu’il audite :

« L’organisation habituelle par couches (Controller → Service → Repository) crée des couplages artificiels entre des features qui n’ont rien à voir les unes avec les autres. »

Sa proposition : organiser le code verticalement par feature, pas horizontalement par couche technique.

Chaque feature contient tout ce dont elle a besoin : sa requête, sa validation, son handler, son accès aux données, sa réponse.

features/
  CreateOrder/
    CreateOrderCommand.cs
    CreateOrderValidator.cs
    CreateOrderHandler.cs
    CreateOrderResponse.cs
  CancelOrder/
    CancelOrderCommand.cs
    CancelOrderHandler.cs

C’est la Vertical Slice Architecture - ou VSA. Une feature = une tranche verticale à travers toutes les couches.

2015-2020 : Domain-Driven Design Devient Mainstream

En parallèle, Domain-Driven Design (DDD) d’Eric Evans - publié en 2003 mais longtemps réservé à l’élite - devient plus accessible grâce aux travaux de Vaughn Vernon, Greg Young et Scott Millet.

Le concept central : organiser le code autour des Bounded Contexts - des frontières explicites entre domaines métier.

Un système de e-commerce ne pense pas « utilisateur » de la même façon dans le contexte des commandes, de la facturation et du support. Ce sont trois contextes distincts, avec leurs propres règles, leurs propres vocabulaires, leurs propres modèles.

L’idée domain-first (ou package by feature) en découle naturellement :

src/
  ordering/      <- tout ce qui concerne les commandes
  billing/       <- tout ce qui concerne la facturation
  inventory/     <- tout ce qui concerne le stock
  users/         <- tout ce qui concerne les utilisateurs

2020-2025 : La Synthèse - Modular Monolith

La synthèse de ces idées donne le Modular Monolith :

  • Un seul déploiement (monolithe)
  • Des modules fortement cohésifs et faiblement couplés (comme des microservices, mais dans le même processus)
  • Des frontières explicites entre domaines (DDD Bounded Contexts)
  • Organisation par feature ou slice (Vertical Slice Architecture)

Kijana Woodard, Sam Newman, Mauro Servienti et de nombreux architectes commencent à documenter et populariser ce pattern sous ce nom.


Qui Utilise Cette Architecture ?

Shopify

Shopify est l’exemple canonique. Leur monolithe Rails gère des milliards de transactions. Plutôt que de migrer vers des microservices, ils ont investi dans la modularisation de leur monolithe : des modules avec des interfaces explicites, des dépendances déclarées, des frontières respectées.

Leur outil interne Packwerk impose ces frontières à la compilation - un module ne peut pas appeler directement les classes d’un autre sans passer par son API publique.

Stack Overflow

L’un des sites les plus consultés du monde tourne sur quelques serveurs et un monolithe .NET. Leur approche est explicitement modular : des projets .NET séparés par domaine, des interfaces claires, une discipline interne stricte.

Basecamp / Hey

DHH (David Heinemeier Hansson) et l’équipe de Basecamp défendent activement le monolithe modulaire depuis des années. Basecamp et Hey, deux produits distincts, tournent sur des monolithes Rails organisés par domaine métier.

GitHub (avant la migration partielle)

GitHub a longtemps fonctionné sur un monolithe Rails. Quand ils ont migré certaines parties vers des services, ils ont été explicites sur les raisons : des problèmes de scalabilité très spécifiques, pas une décision idéologique.

Équipes .NET et Java Enterprise

Dans l’écosystème .NET, le pattern est très répandu grâce à des frameworks comme MediatR (CQRS + VSA), Ardalis.Modulith et les Aspire components de Microsoft.

En Java, Spring Modulith (officiellement supporté par Spring depuis 2023) fournit les outils pour construire et vérifier la modularité d’une application Spring Boot.


Structure Concrète : À Quoi Ça Ressemble

Approche 1 : Package by Feature (la base)

Le minimum viable du modular monolith : organiser par domaine, pas par couche technique.

Avant (organisation horizontale) :

src/
  controllers/
    UserController.ts
    OrderController.ts
    ProductController.ts
  services/
    UserService.ts
    OrderService.ts
    ProductService.ts
  repositories/
    UserRepository.ts
    OrderRepository.ts
    ProductRepository.ts
  models/
    User.ts
    Order.ts
    Product.ts

Après (organisation verticale) :

src/
  users/
    UserController.ts
    UserService.ts
    UserRepository.ts
    User.ts
    index.ts          <- API publique du module
  orders/
    OrderController.ts
    OrderService.ts
    OrderRepository.ts
    Order.ts
    index.ts
  products/
    ProductController.ts
    ProductService.ts
    ProductRepository.ts
    Product.ts
    index.ts

Approche 2 : Vertical Slice Architecture (VSA)

Poussée plus loin, chaque feature est une unité autonome.

src/
  orders/
    create-order/
      CreateOrderRequest.ts
      CreateOrderValidator.ts
      CreateOrderHandler.ts
      CreateOrderResponse.ts
    cancel-order/
      CancelOrderRequest.ts
      CancelOrderHandler.ts
    get-order/
      GetOrderQuery.ts
      GetOrderHandler.ts
      GetOrderResponse.ts
    shared/
      Order.ts              <- modèle partagé dans le module
      OrderRepository.ts

Chaque slice est indépendante. Modifier « annuler une commande » ne peut pas casser « créer une commande » - elles ne partagent que le modèle de domaine.

Approche 3 : Modular Monolith avec Frontières Explicites

Le niveau suivant : des modules comme des « mini-applications » avec leurs propres interfaces.

src/
  modules/
    ordering/
      api/              <- ce que les autres modules peuvent utiliser
        IOrderingModule.ts
        OrderDto.ts
      internal/         <- implémentation privée
        domain/
          Order.ts
          OrderItem.ts
        application/
          CreateOrderUseCase.ts
          CancelOrderUseCase.ts
        infrastructure/
          OrderRepository.ts
          OrderMapper.ts
      OrderingModule.ts  <- registre et bootstrap du module
    billing/
      api/
        IBillingModule.ts
        InvoiceDto.ts
      internal/
        ...
      BillingModule.ts
    inventory/
      api/
        IInventoryModule.ts
      internal/
        ...
      InventoryModule.ts
  app.ts                <- assemble les modules

La règle fondamentale : un module ne peut jamais importer directement depuis internal/ d’un autre module. Il passe uniquement par api/.

Exemple en Python (Django Modular)

myapp/
  core/             <- infrastructure partagée
  ordering/
    __init__.py     <- expose l'API publique
    models.py
    views.py
    urls.py
    services.py
    tests.py
  billing/
    __init__.py
    models.py
    views.py
    urls.py
    services.py
    tests.py
  inventory/
    __init__.py
    models.py
    views.py
    urls.py
    services.py
    tests.py

Exemple en Java (Spring Modulith)

// Spring Modulith impose la structure de packages
com.example.shop/
  ordering/          <- module Ordering
    OrderingModule.java
    Order.java
    OrderService.java
    internal/
      OrderRepository.java   <- inaccessible aux autres modules
  billing/           <- module Billing
    BillingModule.java
    Invoice.java
    BillingService.java
    internal/
      InvoiceRepository.java
// Spring Modulith vérifie les dépendances à la compilation
@ApplicationModuleTest
class OrderingModuleTests {
    // Si Billing accède à OrderRepository (internal), le test échoue
}

Les Principes Clés

1. Cohésion Forte, Couplage Faible

Ce qui change ensemble doit être ensemble. La page de création d’une commande, la logique de validation, l’accès aux données et la réponse HTTP forment une unité cohésive. Les séparer dans des couches différentes crée un couplage artificiel.

2. Frontières Explicites

Chaque module expose une API publique et cache son implémentation. Les autres modules n’accèdent qu’à cette API. C’est la même règle qu’entre microservices - mais sans le réseau.

3. Autonomie des Features

Une feature doit pouvoir être comprise, modifiée et testée sans toucher à d’autres features. Si changer « annuler une commande » nécessite de comprendre « créer une commande », la séparation est incorrecte.

4. Communication par Événements Entre Modules

Quand un module a besoin d’informer un autre d’un événement, il publie un événement de domaine - il ne l’appelle pas directement.

// Ordering publie un événement
eventBus.publish(new OrderCreatedEvent(orderId, customerId, total));

// Billing s'abonne à cet événement
eventBus.subscribe(OrderCreatedEvent, (event) => {
  billingService.createInvoice(event.orderId, event.total);
});

Les modules restent découplés. Si Billing n’existe pas, Ordering fonctionne quand même.


Modular Monolith vs Microservices : Le Vrai Comparatif

AspectMonolithe traditionnelModular MonolithMicroservices
Déploiement1 unité1 unitéN unités
Complexité opérationnelleFaibleFaibleHaute
Frontières de modulesInformellesExplicites et vérifiéesRéseau + API
Transaction ACIDNativeNativeSaga pattern requis
DebuggingSimpleSimpleDistribué, complexe
ScalabilitéTout ou rienTout ou rienIndépendante par service
Migration vers microservicesDifficileNaturelleDéjà distribué

Le modular monolith occupe une position stratégique : il donne les bénéfices de séparation des microservices (isolation, cohésion, frontières) sans le coût opérationnel.

Et si demain un module doit être extrait en service indépendant ? Les frontières sont déjà définies. La migration est une opération chirurgicale, pas une réécriture.


Sinra et le Modular Monolith

Chez Sinra, cette architecture n’est pas théorique - c’est le fondement de notre base de code.

Sinra est un outil de gestion de projet. Il gère des issues, des capabilities, des releases, des cycles, des projets, des équipes, des testings, des pages. Autant de domaines distincts, avec leurs propres règles métier, leurs propres workflows, leurs propres représentations.

Pourquoi le Modular Monolith s’imposait

Nos domaines ont des cycles de vie différents. La logique de création d’une release est indépendante de la logique de gestion des cycles. Un changement dans les testings ne devrait pas impacter les issues.

Notre équipe est de taille raisonnée. Les microservices optimisent pour des équipes de 50+ développeurs qui doivent déployer indépendamment. À notre taille, cette complexité serait un overhead pur.

Nos transactions sont fréquemment cross-domaine. Fermer un cycle implique de mettre à jour les issues non terminées, de notifier les capabilities impactées, de mettre à jour la release associée. Avec des microservices, chaque opération de ce type deviendrait un Saga distribué. Avec un modular monolith, c’est une transaction ACID simple.

La traçabilité est au cœur du produit. Sinra permet de voir l’état exact d’un projet à n’importe quel moment. Le debugging d’un état incohérent est incomparablement plus simple quand tout se passe dans le même processus.

Notre Organisation

app/
  domains/
    project_management/   <- issues, capabilities, cycles, releases
      issues/             <- décorateurs, extensions de collection
      capabilities/       <- services d'assignation, queries
      cycles/             <- value objects, queries
      releases/           <- calculateurs de capacité, value objects
    table_filtering/      <- commandes, queries, stratégies, opérateurs
    tenancy/              <- lifecycle organisation (setup, destroy)
  shared/
    sorting/              <- services et stratégies de tri
    mentions/             <- extraction et traitement des mentions
  models/                 <- modèles Rails (issues, teams, testings, pages...)
  controllers/            <- controllers minces, logique dans les domaines

Les domaines métier regroupent la logique complexe par contexte : project_management concentre tout ce qui touche au travail planifié, table_filtering isole le moteur de filtrage avancé, tenancy gère le cycle de vie des organisations. Les modèles Rails restent le cœur de l’accès aux données - les domaines viennent se greffer pour encapsuler la logique qui dépasse le modèle seul.

Ce que ça change concrètement pour nos utilisateurs

La cohérence des données est garantie. Quand vous assignez une capability à une release, le lien entre les issues sous-jacentes et cette release est toujours cohérent - pas de synchronisation asynchrone, pas d’état intermédiaire.

Les performances sont prévisibles. Pas de latence réseau entre modules, pas de timeout inter-services, pas de retry storms à gérer.

Le développement est rapide. Ajouter une feature dans le module testings n’implique pas de déployer un nouveau service, de configurer un contrat d’API, de gérer la compatibilité entre versions.


Quand Utiliser le Modular Monolith

Cette architecture est le bon choix quand :

  • Votre domaine est complexe mais votre équipe n’est pas encore une organisation de 200 développeurs
  • Vous avez besoin de cohérence transactionnelle entre plusieurs domaines
  • Votre surface de déploiement doit rester simple : une équipe DevOps limitée, un budget infrastructure contraint
  • Vous construisez un produit en phase d’exploration : les frontières de domaine évoluent vite en début de projet
  • Vous voulez la possibilité de migrer vers des microservices plus tard sans réécrire

Ce n’est pas le bon choix si :

  • Vous avez des parties du système avec des besoins de scalabilité radicalement différents (le traitement vidéo de Netflix n’a pas les mêmes besoins que l’API de recommandations)
  • Vos équipes doivent déployer indépendamment à haute fréquence
  • Vous avez déjà des contraintes techniques qui imposent des technologies différentes par domaine

Conclusion

Le modular monolith n’est pas une architecture de compromis. C’est une architecture adaptée à un contexte précis : des domaines complexes, des équipes de taille raisonnée, des besoins de cohérence transactionnelle, une volonté de rester opérationnellement simple.

L’industrie a passé dix ans à croire que la complexité du système devait être gérée par la distribution. On redécouvre progressivement qu’elle peut être gérée par la structure interne.

Vertical Slice Architecture, domain-first, package by feature : ces termes différents décrivent la même idée fondamentale. Le code doit être organisé autour de ce qu’il fait, pas de comment il est techniquement structuré.

Chez Sinra, ce choix n’est pas nostalgique. C’est délibéré, justifié et assumé. Et il nous permet de livrer des features cohérentes, fiables, et traçables - ce qui est précisément ce que Sinra promet à ses utilisateurs pour gérer leurs propres projets.


Sinra organise le travail d’équipe autour d’issues, de capabilities, de releases et de cycles - des concepts concrets plutôt que du jargon abstrait. L’architecture de Sinra suit le même principe : des modules concrets, des frontières explicites, une organisation qui reflète le métier.

Prêt à Transformer Votre Gestion de Projet ?

Appliquez ces insights avec Sinra - la plateforme unifiée pour les équipes modernes.

Commencer l'Essai Gratuit