Une architecture microservices n'est pas toujours la bonne solution

La migration des architectures monolithiques vers des architectures distribuées occupe un grand nombre d'architectes et de développeurs, aujourd'hui. Sachez qu'il est possible de procéder par étapes, avec une bonne conception… et un cadre de développement adéquat.

Une architecture microservices n'est pas toujours la bonne solution
Des abeilles robots, dans le confort de leur ruche commune.

Lors de la conception d'un nouveau système informatique, la tentation est forte d'opter pour le style d'architecture "à la mode". Comme une architecture distribuée, modulaire. En effet, qui voudrait renier la promesse de l'évolutivité, de la résilience et d'une extensibilité à toute épreuve ?! Et pourtant, c'est la pire façon de commencer un projet informatique et ce, quelle qu'en soit l'échelle !

Que le projet soit modeste ou ambitieux, un commencement est un moment d'une délicatesse extrême (oui, je cite le film Dune de David Lynch, en passant). Déjà, on est censé avoir défini une vision claire… Mais que serait la vie sans incertitude ? Et puis on doit organiser le travail d'une ou plusieurs équipes de développement, c'est-à-dire le backlog produit. D'une part, l'éclatement des fonctionnalités peut donner du fil à retordre même à une équipe réduite, mais d'autre part il est courant de voir s'écharper les équipes entre elles lorsqu'elles sont plus nombreuses. À propos des contrats d'API, notamment. Le diable est dans les détails, si l'on peut dire, et les architectures modulaires ont une fâcheuse tendance à multiplier les détails !

Que faire alors ? Doit-on renoncer à l'agilité, en optant systématiquement pour un bon vieux monolithe (et pourquoi pas un système mainframe, pendant qu'on y est ! Hum… pardon) ? D'une certaine façon, il suffirait d'attendre que la dette technique s'accumule, pour justifier le coût d'une refonte au bout de quelques années. Ce qui ne paraît pas être une attitude très professionnelle, certes. Ou alors, on peut poser les bases de la modularité d'entrée de jeu, avec une architecture de "monolithe modulaire" ; à savoir, un système informatique cohérent conçu avec une séparation des préoccupations (SoC) claire. Cette approche garantissant une coopération forte entre les développeurs, qui sont amenés à s'entendre sur une pile technologique et la structuration des données.

Ce n'est que lorsque le poids de la cohérence, ou les enjeux de performance, vont peser sur les développements, qu'on va avoir besoin de séparer les déploiements des différentes fonctionnalités du système d'information. Mais au début, la production de valeur métier, la normalisation des développements (plus compréhensibles) et le partage de la vision technique comptent plus que les performances futures.

La recette de base

Les règles d'implémentation d'un monolithe modulaire peuvent varier selon les besoins spécifiques du projet, mais voici quelques principes généraux qui sont souvent suivis :

  • L'application est divisée en modules basés sur les différentes fonctionnalités ou domaines métier (comme cela sent bon le Domain-Driven Design, soudain). Chaque module doit endosser une responsabilité claire et être autonome dans son exécution, notamment par la mise en place de coupe-circuits.
  • Chaque module doit pouvoir fonctionner de manière isolée, sans dépendre étroitement des autres modules, par exemple pour son lancement. Cela facilite les tests unitaires et les modifications.

REM: du point de vue de la compilation, il est parfois nécessaire de partager des objets et des librairies communs qui sont alors extraits, à la manière d'un web service stub (si quelqu'un se souvient de SOAP).

  • Les modules communiquent entre eux par des interfaces claires spécifiant les méthodes ou les protocoles utilisés pour l'échange d'informations entre les modules. Pour y voir clair, on a souvent recours à un médiateur d'échanges.
  • Chaque module encapsule ses propres données qui ne sont pas exposées directement aux autres modules. Cela favorise la cohésion interne du module et prévient les accès non autorisés ou incohérents aux données.
  • Bien que l'application soit une entité monolithique, chaque module doit être conçu de manière à pouvoir être déployé indépendamment. Cela permet des mises à jour plus granulaires et facilite le déploiement continu.

L'intégration poussée des modules

Pour répondre à la dernière recommandation formulée ci-dessus, qui peut paraître contradictoire, on doit se rappeler que la conception est séparée de l'exécution par les détails de l'implémentation. Dans une architecture distribuée, les différents composants de l'application sont déployés sur des machines distinctes, éventuellement situées sur des réseaux différents. Ils communiquent avec les autres modules via des appels réseau ou des messages.

En revanche, dans un monolithe modulaire, bien que les modules puissent être déployés indépendamment, ils le sont généralement sur la même infrastructure ou la même machine. Il faut donc que chaque module puisse être mis à jour et redéployé de manière isolée, alors que l'ensemble de l'application est exécuté en tant qu'entité unique, partageant la même base de code et les mêmes ressources système.

Bien que les deux approches cherchent à découpler les fonctionnalités en modules distincts, la différence clé entre un monolithe modulaire et une architecture distribuée réside donc dans le lieu de déploiement des modules, et les mécanismes de communication entre eux. Si les modules sont conçus pour être déployés indépendamment les uns des autres, cela signifie que nous avons besoin d'un environnement d'exécution susceptible de permettre le remplacement à chaud des éléments au sein d'un système centralisé. Levez les bras au ciel et dites "injection de dépendances", "inversion de contrôle" !

C'est d'ailleurs comme cela que fonctionnent les briques Dynamic-Link Library (DLL) de Microsoft et les shared object files Sun/UNIX.

Or, il se trouve que le projet (enfin publié en version 1.0 GA cet été) Spring Modulith promet aux développeurs de créer des applications Spring Boot 3 structurées de cette manière (hello, Java 9 Platform Module System). Il les guide dans la découverte et l'utilisation de modules d'application pilotés par le domaine, mais il prend en charge la vérification de ces arrangements modulaires (par exemple via ArchUnit), l'intégration des tests de modules particuliers. Il propose aussi l'observation du comportement de l'application au niveau du module et même la création de snippets dans la documentation, sous la forme de diagrammes de composants, avec PlantUML.

Notons que bien que l'injection de modules soit envisageable, Spring Modulith encourage l'utilisation des événements applicatifs de Spring Framework pour la communication entre eux. Pour les garder aussi découplés que possible les uns des autres, la publication et la consommation d'événements internes comme principal moyen d'interaction est une pratique architecturale qui a de l'avenir ! En effet, sur cette base, il est d'autant plus facile de basculer vers une architecture distribuée en s'appuyant sur les meilleurs brokers du marché.

Spring Modulith
Level up your Java code and explore what Spring can do for you.
Pour en apprendre plus sur Spring Modulith

Génial ! Vous vous êtes inscrit avec succès.

Bienvenue de retour ! Vous vous êtes connecté avec succès.

Vous êtes abonné avec succès à WENVISION.

Succès ! Vérifiez votre e-mail pour obtenir le lien magique de connexion.

Succès ! Vos informations de facturation ont été mises à jour.

Votre facturation n'a pas été mise à jour.