Ton monolithe Rails est hors de contrôle ? Pense aux engines avant de tout casser

Arnaud Lenglet
|
Ruby on Rails
|
23/9/2024

Tu as sans doute déjà entendu parler des engines Rails, souvent évoqués comme des composants mystérieux et un peu effrayants du framework. Beaucoup les voient comme un outil réservé aux architectes chevronnés ou aux projets tentaculaires. Résultat ? On les évite comme la peste, et on continue à empiler du code dans des monolithes toujours plus gros et plus lourds.

Pourtant, si je te disais que les engines sont en fait une solution élégante pour organiser ton application Rails, pour la rendre plus modulaire, plus maintenable et, surtout, plus facile à faire évoluer ? Oui, tu peux littéralement construire des mini-applications dans ta propre app Rails. Le rêve des microservices sans la complexité ! Et si tu veux éviter la galère des migrations monstrueuses ou du code spaghetti, les engines sont exactement ce qu’il te faut.

Bon, avant de t’imaginer diviser ton app en cent morceaux, voyons de plus près ce qu’est un engine Rails et pourquoi c’est un des outils les plus sous-estimés de l’arsenal Rails.

1. Un engine = la plus petite app Rails possible

Commençons par le début : un engine, c’est littéralement une mini-application Rails. Imagine ton app Rails habituelle, avec ses modèles, contrôleurs, routes, vues, migrations… Eh bien, un engine, c’est exactement la même chose, mais en version réduite. Ce qui le rend spécial, c’est qu’il peut être intégré dans une autre application Rails, comme une brique modulaire.

En gros, au lieu de balancer tout ton code dans le même seau, tu peux diviser les responsabilités en engines qui fonctionnent chacun de leur côté, avec leurs propres règles, leurs propres routes, et même leurs propres assets (si tu en as besoin). C’est comme si tu créais plusieurs petites applications autonomes qui peuvent s’imbriquer dans ton monolithe principal.

Un exemple pour y voir plus clair

Prenons un exemple simple : imaginons que tu développes une application de gestion de projet. Plutôt que d’avoir un seul gros projet Rails où toutes les fonctionnalités sont mêlées, tu peux créer un engine spécifique pour la gestion des utilisateurs, un autre pour la gestion des tâches, et un autre pour les notifications. Chaque engine gère sa propre logique, et tout ça peut être embarqué dans ton application principale.

En termes de code, ça donne quelque chose comme ça pour ton engine :

rails plugin new notifications --full

Voilà, tu as créé un engine Rails ! Il a sa propre structure de répertoires avec app, config, lib… comme une vraie app Rails. Mais ce qui est encore plus intéressant, c’est que cet engine peut fonctionner de manière totalement autonome dans ta base de code principale. Tu peux même le sortir pour l’utiliser dans d’autres projets Rails. Flexibilité maximale.

2. Quand as-tu besoin d’un engine ?

Ok, tu sais maintenant qu’un engine est une mini-application Rails, autonome et modulaire. Mais la vraie question, c’est : quand est-ce que tu devrais en mettre un en place ? Parce que bon, soyons honnêtes, découper ton code en morceaux juste pour le plaisir de faire des engines, ce n’est pas très malin. Il faut savoir pourquoi tu le fais et à quel moment c’est pertinent.

Quelques signaux d’alerte

Certaines situations devraient te faire penser aux engines comme une solution potentielle. Voici quelques red flags qui devraient t'alerter :

  • Ton application devient gigantesque : Si ton app commence à ressembler à un monstre avec des centaines de modèles et contrôleurs qui interagissent tous entre eux, il est peut-être temps de commencer à modulariser.
  • Les responsabilités sont floues : Si tu trouves que les différentes parties de ton app (par exemple, la gestion utilisateur, le paiement, la gestion de contenu) s’entremêlent dangereusement et deviennent difficiles à isoler, un engine pourrait t’aider à clarifier les rôles.
  • Tu t’apprêtes à réutiliser une fonctionnalité ailleurs : Si une partie de ton application pourrait être utilisée dans d'autres projets Rails ou si tu veux que certaines fonctionnalités soient indépendantes (un moteur de recherche, par exemple), c'est le moment de penser à les isoler dans un engine.
  • Ton équipe grossit : Plus il y a de développeurs sur ton projet, plus il devient critique de séparer les responsabilités. Les engines permettent à des équipes différentes de travailler sur des modules distincts sans trop marcher sur les pieds des autres.

Comment découper ta base de code

Le vrai challenge, c’est de savoir découper. Ne t’amuse pas à créer un engine pour chaque petit bout de code : c’est une recette pour le chaos. L’idée, c’est de modulariser des blocs cohérents, qui ont une responsabilité claire et qui, idéalement, peuvent être indépendants de ton application principale.

1. Regroupe par fonctionnalité métier

La première approche consiste à identifier les domaines fonctionnels de ton application. Si tu développes un outil de gestion de projets, comme dans notre exemple précédent, tu pourrais avoir un engine pour la gestion des utilisateurs, un autre pour les tâches, un autre encore pour la gestion des documents, etc. Chacun de ces engines est responsable d’un pan fonctionnel complet, avec sa propre logique métier, ses routes et ses modèles. Cela permet de limiter la complexité dans ton app principale.

2. Regarde les dépendances

Une autre stratégie pour découper ton code, c’est d’identifier les dépendances entre différentes parties de ton app. Si tu remarques que certaines fonctionnalités ne dépendent de presque rien d’autre dans ton application (par exemple, un moteur de recherche, ou une API interne), c’est probablement un bon candidat pour être isolé dans un engine. Moins il y a de liens avec le reste du projet, plus il sera facile à découper proprement.

3. Ne découpe pas trop tôt !

Cela dit, ne tombe pas dans le piège de sur-modulariser trop tôt. Si ton application est encore relativement simple et que les interactions entre les différentes parties restent claires, tu n’as pas besoin de te précipiter sur les engines. Le but est de découper quand la complexité le demande, pas avant.

Un petit test pratique

Pose-toi la question : Est-ce que cette partie de mon application pourrait vivre seule ? Si la réponse est oui, alors c’est probablement un bon candidat pour un engine. En revanche, si ton module dépend fortement de beaucoup d’autres parties de ton app, il vaut peut-être mieux le laisser là où il est, jusqu’à ce qu’il devienne plus autonome.

3. Exemple concret : une application Rails découpée en engines

Pour illustrer l’utilisation des engines Rails, rien de mieux qu’un cas pratique. Imaginons un projet d’application pour la gestion d’un dispositif IoT (Internet of Things). Dans ce projet, nous devons gérer plusieurs aspects différents du device : l’envoi de commandes, la gestion des composants physiques, les mises à jour, et la communication avec un backend ou une app compagnon.

Dans un projet classique, on pourrait être tenté d’intégrer tout ça dans une seule et unique application Rails, avec des contrôleurs et des modèles mélangés. Mais ici, on va opter pour une approche plus propre et plus modulaire, en séparant chaque fonctionnalité clé en engines. Cela permettra une meilleure organisation, une meilleure réutilisabilité, et surtout une plus grande maintenabilité à long terme.

Présentation du projet

L’application, disponible sur GitHub ici : [lien placeholder], s’appelle device_os. Comme son nom l’indique, elle est conçue pour gérer un device IoT. Chaque aspect du fonctionnement du device est isolé dans un engine indépendant, ce qui permet de séparer clairement les responsabilités et de rendre le code plus facile à maintenir et à tester.

Voici l’arborescence du projet pour que tu comprennes comment sont organisés les différents engines :

device_os
| - bin
| - config
| - db
| - engines
    | - command
        | - app
        | - config
        | - lib
            | - engine.rb
        | - spec
    | - hardware
        | - ...
    | - maintenance
        | - ...
    | - communication
        | - ...
| - ...

Chaque engine est placé dans le dossier engines, et comme tu peux le voir, chacun possède ses propres dossiers app, config, lib, et spec. Chaque module est donc presque une application Rails à part entière, avec ses propres routes, contrôleurs et modèles, mais ils peuvent tous être intégrés dans l'application principale pour fonctionner ensemble.

Rôle de chaque engine

Voyons maintenant le rôle spécifique de chaque engine dans ce projet.

  • Engine "command" : Cet engine est responsable de la gestion des commandes entrantes. Il reçoit des ordres d’activation ou de désactivation de fonctionnalités sur le device et gère leur exécution. Il peut, par exemple, activer le vibreur, allumer une LED, ou même couper l’alimentation.
  • Engine "hardware" : Il s’occupe de gérer tous les composants physiques du device. Que ce soit des vibreurs, des LED, des haut-parleurs ou des capteurs, c’est ici que tu vas trouver la logique qui contrôle ces éléments. Grâce à cet engine, tout ce qui touche au hardware est bien isolé de la logique métier.
  • Engine "maintenance" : Responsable de la gestion des mises à jour et des réinitialisations du device, cet engine s’occupe des tâches de maintenance. Il gère l’application des mises à jour, le redémarrage du device, et peut même réinitialiser celui-ci à ses paramètres d’usine si nécessaire.
  • Engine "communication" : Comme son nom l’indique, cet engine est dédié à la gestion des communications. Il permet de connecter le device à son backend, de communiquer avec une application compagnon, et même de gérer la connexion à Internet. C’est ici que se trouvent les tests de connectivité et les procédures de configuration réseau.

L'intérêt d'utiliser des engines dans ce projet

Dans ce cas précis, l’utilisation des engines prend tout son sens. En effet, on a plusieurs fonctionnalités bien distinctes qui doivent coexister, mais sans interférer les unes avec les autres. Grâce aux engines, on peut isoler chaque bloc de fonctionnalités de façon modulaire. Cela permet de :

  1. Isoler les responsabilités : Chaque engine gère une partie spécifique du fonctionnement du device. Ainsi, les commandes, le hardware, la maintenance et les communications sont chacun découpés en modules indépendants. Cela facilite la lisibilité du code et évite les mélanges dangereux.
  2. Encapsulation : Chaque engine a son propre espace de nom, ce qui permet de garder les différentes parties bien séparées. Par exemple, les routes de l’engine command n’interfèrent pas avec celles de hardware ou maintenance, même si elles font partie de la même application.
  3. Réutilisation potentielle : Ces engines sont conçus de manière à pouvoir être utilisés dans d’autres projets Rails si nécessaire. Si, dans le futur, une autre application IoT a besoin d’une gestion de commandes similaire, on pourrait réutiliser l’engine command sans avoir à réécrire tout le code.

En divisant ainsi l’application en engines, on obtient une structure modulaire et maintenable. Chaque engine fait une chose, mais il le fait bien, sans être influencé par d’autres parties du code. Cela rend la base de code beaucoup plus facile à gérer au fil du temps, surtout si elle devient complexe.

Conclusion

Les engines Rails ne sont pas juste une curiosité ou une option exotique pour les développeurs les plus téméraires. Ils représentent une solution élégante et puissante pour structurer des applications complexes de manière modulaire. Comme on l’a vu avec notre projet IoT, ils permettent d’isoler les responsabilités, d'encapsuler des fonctionnalités, et d'offrir une base de code maintenable et évolutive, sans sacrifier la simplicité propre à Rails.

Le tout, c'est de savoir quand les utiliser et surtout où découper son application. Si ton monolithe commence à ressembler à une jungle inextricable, il est peut-être temps d’envisager les engines. Mais avant de foncer tête baissée, n’oublie pas : un découpage intelligent est clé, sans quoi tu risques de te retrouver avec des microservices déguisés.

Rails n’est peut-être pas aussi rigide que d'autres frameworks en termes de modularité, mais avec un peu de stratégie, tu peux structurer ton application pour qu’elle soit non seulement fonctionnelle aujourd'hui, mais aussi prête à grandir demain, sans plomber tes nuits (et celles de ton équipe).

En somme, les engines, c’est comme les outils bien affûtés : au début, tu les trouves un peu intimidants, mais une fois que tu les maîtrises, tu ne peux plus t’en passer. Alors, à quand ton premier engine ?

Arnaud Lenglet
Développeur. Concepteur. Créateur.
Je partage mes expériences autour de la création de produit.

Sur la même thématique

Une idée ? Un projet ?
Travaillons ensemble