Modular Monolith is the new Microservices
Parler de monolithe modulaire en fin d'année 2023, c'est arriver un peu après la bataille. Et tant mieux : son principe, plein d'un pragmatisme revigorant, a fait des émules. Plus personne ne déploierait son MVP sur Kubernetes (K8S) pour une application comptant une centaine d'utilisateurs.
Plus personne ? 😉
Rappelons pour commencer ce qu'est un monolithe modulaire :
-
Le monolithe modulaire s'oppose au monolithe "plat de spaghettis" par sa structuration claire en modules fonctionnels. Cela signifie qu'une vraie démarche de DDD stratégique a été menée en amont pour faire émerger les bounded contexts. On sait où l'on met les pieds. On en fait peu, mais bien.
-
Mais dans le même temps, le monolithe modulaire conserve la facilité de déploiement du monolithe, puisque les différents modules sont déployés au sein d'une seule et même "coquille". Conséquence immédiate : redéployer un module signifie redéployer tous les modules.
La beauté du monolithe modulaire, c'est qu'il n'obère pas le futur : il sera toujours temps de passer en microservices si le besoin se fait sentir. Quand vous serez Google ou Netflix et que vous aurez des vrais problèmes de scalabilité 😉
Cependant, une fois qu'on a dit ça, on n'a pas dit grand-chose. D'expérience, la mise en œuvre d'un monolithe modulaire soulève des questions. Voici 4 enseignements qui me semblent particulièrement importants :
-
Isoler le domaine à l'intérieur de chaque module, via une architecture hexagonale ou bien functional core / imperative shell. Ainsi, votre monolithe est une juxtaposition de jolis petits hexagones bien rangés les uns à côté des autres, même si l'API HTTP et l'infrastructure sont mutualisées.
-
Si les différents contextes requièrent le même type de base de données (relationnel, document, graphe, clé-valeur etc.), cette dernière peut être mutualisée. Cependant, on voudra y retrouver l'organisation en contextes. Pour une base SQL, par exemple, l'idéal sera de créer un schéma par contexte.
-
Et interdiction pour un contexte d'aller lire ou écrire en base dans le schéma du petit copain. Non, on fait proprement un appel à l'API exposée par le contexte en question. Comme pour un objet, accéder directement aux données d'un contexte serait une violation de responsabilité ("tell, don't ask").
-
Quand je dis "API", je ne parle pas d'API HTTP mais de l'API exposée par le cœur-domaine du contexte. Pour interroger le contexte voisin, on ne va quand-même pas sortir sur le réseau : on fait un appel de fonction.
On pourrait en fait déroger au 3ème point en mettant en place une communication événementielle via un bus de données synchrone, pour un meilleur découplage entre contextes. Chaque contexte collecte ainsi les informations qui lui seront utiles pour ses futurs traitements (j'aime l'idée que la donnée "sédimente"). Cette modalité facilite la transition vers une chorégraphie de microservices – par opposition à l'orchestration.
Ne voyant pas d'heuristique évidente, j'éviterais de mélanger les modalités de communication : appels de fonctions et communication événementielle.