Technologie

Comment nous avons transformé le backend unique d'Omnilude en MSA avec l'aide de l'IA

AAnonymous
12 min de lecture

Introduction

Cet article fait suite à Voici le backend que nous construisons pour Omnilude. Dans l'article précédent, j'ai présenté la structure actuelle du backend d'Omnilude et ses frontières de responsabilité comme s'il s'agissait d'un seul backend produit. Cette fois, je voudrais expliquer comment cette structure a réellement commencé à se séparer, d'un grand projet unique vers plusieurs frontières de services.

Les discussions autour de la MSA paraissent souvent trop abstraites. Il est facile de dire qu'un système a été découpé, qu'il peut être déployé indépendamment ou que des frontières ont été définies. Il est beaucoup plus difficile de comprendre ce qui a réellement changé dans un projet vivant.

Le backend d'Omnilude offre justement assez de matière concrète pour raconter ce changement. Aujourd'hui, il est découpé en auth-service, backbone-service, storage-service, ai-service, game-service, blog-service et legacy-service, mais il n'a pas commencé ainsi. Au départ, c'était un unique grand backend Spring Boot.

Dans cet article, je vais expliquer pourquoi ce projet unique a commencé à se séparer, pourquoi jspring(common) a été extrait en premier, pourquoi legacy-service a été laissé volontairement en place, et quel rôle l'IA a réellement joué dans cette transition.

Au départ, il n'y avait qu'un grand backend

Avant la transition, le backend d'Omnilude était, pour le dire simplement, un endroit où beaucoup de choses étaient entassées dans le même projet. L'authentification s'y trouvait, l'IA aussi, la logique de jeu, le stockage de fichiers, les événements temps réel, ainsi que les utilitaires communs.

Ce genre de structure paraît pratique au début. On ajoute des fonctionnalités rapidement et tout se modifie dans le même codebase. Mais à mesure que le projet grandit, les problèmes apparaissent.

  • Des zones comme l'authentification, qui doivent être manipulées avec prudence
  • Des zones comme l'IA, qui changent souvent et demandent beaucoup d'expérimentation
  • Des zones comme les événements temps réel, où la stabilité est essentielle
  • Des zones comme le jeu, où la logique métier grossit rapidement

Toutes finissent par subir la même pression de déploiement.

Une image simple serait un placard où l'on range en même temps les vêtements de saison, les affaires de sport, les costumes, les couvertures et la boîte à outils. Au début, cela semble pratique. Puis vient le moment où modifier une seule chose devient déjà pesant.

Pourquoi le découper ?

Au départ, je ne poursuivais pas la MSA comme un objectif en soi. Les raisons étaient beaucoup plus concrètes.

La plus importante était de protéger des déploiements ordinaires les fonctions qui doivent conserver des connexions longues. Dans Omnilude, certaines zones étaient très proches de WebSocket, de SSE, des flux d'événements et des traitements asynchrones de longue durée, en particulier autour de backbone-service. Si ces zones étaient perturbées par des modifications sans rapport, l'impact se ressentait immédiatement en production. Une structure dans laquelle une mise à jour du blog ou du jeu pouvait redémarrer des services sensibles à la connectivité n'était pas viable à long terme.

La deuxième raison tenait au temps de déploiement. Même une petite modification signifiait reconstruire l'ensemble de la grosse application, refaire l'image puis redéployer. Plus le projet grandissait, plus ce temps paraissait gaspillé. Si les zones qui évoluent vite restent liées à une seule unité de déploiement, tout le système ralentit avec elles.

Ensuite s'est posé le problème de frontières de responsabilité devenues floues. Les règles d'authentification, les exceptions communes, le HTTP interne, la messagerie et le stockage de fichiers étaient mêlés dans tout le code, au point qu'il devenait difficile de distinguer ce qui relevait du commun et ce qui relevait du métier.

Il y avait enfin une raison plus large : je savais déjà assez clairement ce que je voulais construire ensuite. Dans Omnilude, je voulais développer davantage les fonctions IA, renforcer le service de jeu et continuer à ajouter des produits séparés comme le blog et les outils. Continuer à faire grandir tout cela dans un seul backend ne me paraissait plus être un bon choix.

Autrement dit, cette transition n'a pas eu lieu parce que la MSA paraissait élégante. Il s'agissait plutôt d'une réorganisation nécessaire pour protéger les services sensibles aux connexions, réduire le temps de déploiement et continuer à pousser des idées plus ambitieuses.

La première étape n'a pas été de couper des services, mais d'extraire les règles communes

Il y a ici un point important. La première étape de cette transition n'a pas été d'extraire auth-service ou ai-service. Avant cela, j'ai créé common.

C'est essentiel. Vu de l'extérieur, le découpage en services ressemble à une séparation de domaines. En pratique, le vrai travail commence avant : il faut d'abord définir ce qui constitue le contrat partagé et le sortir.

Dans Omnilude, common a assumé ce rôle.

  • Gestion commune des exceptions
  • Conventions JWT
  • DTO partagés
  • Utilitaires de la couche web
  • Fondations JPA et accès aux données
  • Aides pour le HTTP interne et la messagerie

Une fois ces éléments déplacés dans common, les services séparés ont pu continuer à parler le même langage, au lieu de devenir des applications totalement étrangères les unes aux autres.

On peut comparer cela au fait de fixer d'abord les standards des prises électriques et des poignées de porte avant de diviser un bâtiment en plusieurs pièces. Si ces standards ne sont pas alignés d'abord, séparer les pièces n'améliore pas vraiment la vie quotidienne.

La séparation s'est faite très vite

Si l'on regarde l'historique du dépôt, la transition s'est déroulée sur une période étonnamment courte. L'ossature principale a été mise en place entre le 18 et le 22 janvier 2026.

En simplifiant, le flux a ressemblé à ceci.

  • Extraction de common
  • Séparation de auth-service
  • Séparation de backbone-service
  • Séparation de storage-service
  • Début de la séparation de ai-service
  • Isolement du reste dans legacy-service
  • Extraction de game-service
  • Ajout ensuite de blog-service comme nouveau service
  • Réorganisation de Jenkins et Kubernetes autour du déploiement sélectif

Cet ordre avait sa logique.

auth-service avait des frontières relativement claires. L'authentification, les comptes et les permissions sont des dépendances partagées par les autres services, donc l'extraire tôt a créé un point de référence solide pour la suite.

backbone-service avait un rôle plus proche de l'infrastructure, avec les événements temps réel, la messagerie et les préoccupations autour du DTE. L'isoler de la logique produit qui évolue vite avait beaucoup de sens du point de vue opérationnel.

storage-service devait porter le contrat partagé de stockage de fichiers. Une fois la gestion des fichiers centralisée, le blog, le jeu et les résultats générés par l'IA pouvaient suivre le même flux.

Puis est venu ai-service. Cette zone était plus difficile car plus grosse et plus dépendante d'autres composants. Mais c'était aussi l'une des parties où je comptais le plus expérimenter, donc la traiter comme un service indépendant était logique.

Pourquoi conserver legacy-service ?

Quand on imagine une migration vers la MSA, on imagine souvent une fin propre où le monolithe disparaît d'un seul coup. En pratique, cette image est bien moins réaliste qu'elle n'en a l'air.

C'est pour cela qu'Omnilude a regroupé dans legacy-service les domaines qui n'étaient pas encore complètement extraits.

Cette décision n'était pas vraiment un recul. Elle ressemblait plutôt à la création d'une zone tampon.

legacy-service a rendu plusieurs choses possibles.

  • Ne pas casser de force les migrations inachevées
  • Faire coexister anciens et nouveaux services dans le même produit
  • Continuer à avancer tout en gardant le système exploitable
  • Reporter les domaines les plus difficiles et extraire d'abord les plus simples

Pour moi, c'est même l'un des aspects les plus réalistes d'une migration MSA. Une vraie migration ressemble moins à une séparation parfaite qu'à la création de frontières capables de tenir en exploitation.

Le jeu, le blog et l'IA se sont ensuite clarifiés

À partir de là, les services plus fortement orientés produit ont commencé à prendre une forme plus nette.

ai-service n'est pas une simple API de chat. Il porte l'exécution d'agents, les workflows, les pipelines de génération, la génération de médias, les systèmes de roleplay et les agents produit. À cette échelle, le considérer comme un service indépendant devient beaucoup plus naturel.

game-service se trouvait au plus près de la partie que je voulais finalement développer le plus dans Omnilude. Avec l'ajout des story quizzes, des scénarios et des fonctions de studio d'assets, il est devenu un service centré sur le domaine, reliant IA, stockage et authentification.

blog-service est un cas un peu différent. Il ne s'agit pas simplement d'un domaine extrait directement du monolithe d'origine. Il ressemble davantage à un nouveau service qui a trouvé sa place sur une architecture déjà séparée. Comme le déploiement et les règles communes étaient déjà dissociés, le blog a pu être conçu et exploité comme un service indépendant dès le départ.

C'est aussi pour cela que cette transition MSA n'a pas consisté uniquement à démonter l'ancien. Elle a aussi servi de fondation pour lancer de nouveaux produits dans de meilleures unités.

Le moment où tout a vraiment changé, c'est le déploiement

En regardant seulement le code, un projet peut donner l'impression d'être séparé. Mais le moment où la MSA devient réelle, c'est lorsque le déploiement change.

Dans Omnilude, cela est visible dans Jenkinsfile, Jenkinsfile.prd et kubernetes/services/**.

Aujourd'hui, Jenkins peut déployer sélectivement ces sept services.

  • auth-service
  • backbone-service
  • storage-service
  • ai-service
  • game-service
  • blog-service
  • legacy-service

Le dépôt reste donc un seul dépôt, mais il ne s'agit plus d'une application qui doit toujours être déployée en bloc. Il est devenu possible de livrer seulement le service dont on a besoin.

Kubernetes suit la même logique. Chaque service possède son propre Deployment et son propre Service, tandis que Ingress route sous api.omnilude.com ou dev-api.omnilude.com selon le préfixe de chemin.

Ce point compte vraiment. La MSA ne consiste pas à séparer des dossiers, mais à séparer des unités d'exploitation. Dans Omnilude, ce n'est qu'après avoir franchi cette étape que la séparation a commencé à me sembler réellement accomplie.

Au final, ce n'était pas le nombre de services qui comptait

Après une transition de ce type, il est facile de regarder d'abord combien de services ont été créés. Avec le recul, ce nombre n'était pas l'essentiel.

Les quatre questions suivantes comptaient bien davantage.

  • A-t-on extrait d'abord les règles partagées pour rendre la séparation possible ?
  • A-t-on distingué les domaines qui changent vite des préoccupations d'infrastructure plus lentes ?
  • A-t-on créé des frontières exploitables, même imparfaites ?
  • A-t-on relié la séparation du code à la séparation réelle du déploiement ?

Sous cet angle, la transition d'Omnilude me paraît très pragmatique. Ce n'est peut-être pas le schéma le plus élégant du monde, mais cela a créé une structure capable de porter le produit suivant et l'expérience suivante.

Conclusion

La transition MSA du backend d'Omnilude n'est pas vraiment née d'un grand document stratégique dès le premier jour. Elle ressemble davantage au type de remise en ordre structurelle qu'il faut tôt ou tard traverser lorsqu'on veut construire quelque chose de plus grand.

Tout a commencé par un grand projet, puis common a été extrait, auth, backbone, storage, ai, game et blog ont été séparés, le reste a été conservé dans legacy-service, et enfin les unités réelles de déploiement ont été alignées avec Jenkins et Kubernetes.

Pour moi, cela a quelque chose de très Omnilude. Le nom Omnilude porte à la fois l'ampleur suggérée par omni- et la dimension ludique suggérée par -lude. L'idée est que de nombreuses fonctions et de nombreux services ne restent pas éparpillés pour toujours, mais finissent par converger vers une expérience de jeu plus vaste. En ce sens, cette transition ressemble aussi au nom lui-même : la direction était claire, et la structure a continué à évoluer pour s'y adapter.

Dans le prochain article, je voudrais aller un pas plus loin après l'architecture et raconter comment je mets réellement le jeu en œuvre.