Je suis Jérémy Mathon, développeur fullstack depuis environ 7 ans. Je suis tombé dans le code quand j’étais petit et j’ai décidé d’en faire mon métier. Quand je ne transforme pas du café en code, je suis probablement en train de résoudre des Promises. Je suis rapidement passé lead d’équipe dans ma carrière et j’ai toujours aimé mentorer et aider mon équipe. J’aimerais pouvoir aider au mieux les devs plus juniors à se lancer, on commence tous quelque part !
Lorsque votre codebase commence à avoir de l’ancienneté, il devient compliqué de développer de nouvelles features, de refactorer une partie du code sans casser une autre, etc.
On arrive alors généralement sur un souci global d’architecture de l’application. Parfois, car on n’a pas de vision assez long terme, d’autre fois, car il faut faire du “quick & dirty” pour satisfaire un gros client.
On commence alors à chercher en ligne comment “casser” ce monolithe et permettre de déployer plus facilement son code en production. Ou encore, de mieux définir les “limites” d’une feature quand on commence à travailler en feature squad.
On tombe alors sur une infinité de liens, d’articles et de vidéos parlant des micros services.
Alors oui, les micros services semblent cocher toutes les cases :
- On découpe le code par logique métier et on évite le code “spaghetti” réutilisé à certains endroits où il ne devrait pas.
- Chaque équipe peut travailler sur son propre micro service sans empiéter sur le code d’une autre équipe, évitant ainsi les merge conflicts.
- On peut déployer un service sans devoir déployer toute la stack et éviter des mises en production longues et compliquées.
Or, on confond souvent l’usage des micro services : le fait de passer en micro services ne va pas régler vos soucis d’architecture.
Comme on m’a dit une fois, “Les micro services, c’est une façon de déployer, pas une façon d’architecturer son code”.
De plus, passer d’un énorme monolithe vers plein de micro services, demande énormément d’effort. Du temps de développement, et tant que tout n’est pas bien mis en place, ça ne peut pas être release. Et ça, pour le business, c’est inconcevable.
Sans compter qu’une architecture en micro services a aussi ses problématiques.
Baby step par baby step
Le souci est apparu lorsqu’on était une 40aine de développeurs à travailler sur le même monolithe. Il devenait de plus en plus compliqué de faire évoluer le produit.
Comme dit plus haut, on ne pouvait pas arrêter de développer des features pour passer sur des micro services en plusieurs mois et ainsi réduire grandement notre dette technique.
Nous n’avions pas de gros soucis de performance, il était toujours possible de faire tourner tout le produit sur un serveur. Il fallait trouver une solution intermédiaire.
C’est alors qu’on a songé aux Engines.
Les Engines
Les Rails Engines, ce sont des mini-apps qui ajoute des fonctionnalités à leur app parent. L’Engine gère ses propres routes, views et sa propre logique.
Cette approche nous permet plusieurs choses :
- On peut continuer de refactorer le code actuel dans un Engine. De descendre la logique petit à petit dedans et de gérer le tout pour les clients via des feature flags.
- La notion d’Engine nous permet de bien délimiter la logique business géré par celui-ci et de suivre une notion centrale du Domain-Driven Design (DDD) : le **[Bounded Context](https://martinfowler.com/bliki/BoundedContext.html)**.
- Ça permet de vraiment définir une structure pour la codebase et ainsi arrêter le code “spaghetti”.
- Chaque Engine a un rôle précis et lui seul peut gérer cette data. L’Engine reçoit un Input qui est décoléré de la logique métier (DTO), traite la logique business dans des opérations et retourne à son tour un DTO. Cela simplifie énormément la communication avec l’engine et permet de vraiment scoper la portée de l’Engine.
La première étape pour casser un monolithe Ruby on Rails en Engines consiste à identifier les fonctionnalités qui peuvent être regroupées en modules distincts et isolés. Ces modules peuvent ensuite être extraits du code de l’application principale et convertis en Engines.
Chaque Engine peut ensuite être développé, testé et déployé indépendamment. Ce qui permet une plus grande flexibilité et extensibilité de l’application globale.
Il est important de noter que casser un monolithe en Engines n’est pas une tâche facile. Ça nécessite une planification minutieuse et une bonne compréhension de la structure de l’application. Il peut également y avoir des défis à surmonter en matière de communication entre les différents Engines et la partie principale de l’application.
Mais une fois que cela est fait, cela peut offrir plusieurs avantages. Notamment une plus grande modularité, une meilleure maintenabilité et une évolutivité accrue.
Mise en pratique
On a alors commencé avec un Engine pour gérer tous les uploads de l’app. L’Engine reçoit un fichier, une image, un document,… qui est géré de la même façon avec la notion de DTO et retourne un lien vers l’objet sur notre bucket S3.
On a ensuite développé un Engine pour la gestion des commentaires dans notre application, les Webhooks, la gestion des notifications, l’envoi des emails, etc…
Finalement, après plusieurs mois de transitions, on n’a jamais fait le grand saut vers les micros services. Le fait de ne pas avoir de soucis de performance ne nous oblige pas à faire le pas supplémentaire tout de suite. Notre monolithe modulaire répond aux problématiques auxquels nous étions confrontés.
—
Jérémy Mathon
Jérémy est un contributeur régulier sur WeLoveDevs 💫 Retrouvez son interview et le QCM qu’il a créé !