Pour construire Omnilude, voici le backend que je construis
Cet article n'a pas pour but de présenter une fonctionnalité isolée. Il vise plutôt à expliquer sur quel type de structure repose le projet appelé Omnilude, et pourquoi j'organise le backend de cette manière.
Ce que je fais en ce moment se situe à l'intersection de deux dynamiques. La première consiste à tester les limites des coding agents. Je continue à vérifier jusqu'où l'implémentation peut aller avec des instructions proches du vibe coding et d'un flux de conscience, et si cette manière de travailler peut réellement s'étendre à un développement de niveau production. La seconde consiste à ne pas laisser cette expérimentation rester une simple expérimentation, mais à la faire converger vers un véritable service. Omnilude se trouve au centre de ce service, et c'est sur cette structure que je veux pousser cette expérimentation à travers le type de développement de jeux que j'avais envie de faire depuis longtemps.
Cet article est donc plus proche d'une présentation de structure que d'une démonstration d'implémentation. Mais je ne voulais pas non plus l'expliquer en me contentant d'aligner des noms de services. Ce backend est divisé en plusieurs modules, mais la manière dont je le regarde réellement est légèrement différente. Je vois l'ensemble comme un seul backend produit. L'authentification, l'exécution de l'IA, la gestion de contenu, les connexions temps réel, le stockage de fichiers et les flux d'événements sont tous liés à un même objectif.
Le post sera peut-être un peu long et exigeant, mais je veux l'expliquer de la manière la plus concrète possible.
Quel projet Omnilude cherche-t-il à devenir
Pour moi, Omnilude porte deux significations en même temps.
La première est celle de l'expérimentation. À une période comme celle-ci, je ne pense pas qu'il soit suffisant pour un développeur de simplement essayer de nouveaux outils. L'écart entre ceux qui utilisent bien l'IA et ceux qui l'utilisent mal a de fortes chances de continuer à se creuser, et je pense que cet écart se jouera bientôt moins sur la vitesse d'implémentation que sur la définition du problème, la conception de la structure et la manière de valider. Je ne voulais pas seulement observer ce changement de l'extérieur. Je voulais le traverser pleinement en construisant un produit réel.
La seconde est un objectif produit très concret. Je veux créer des jeux depuis longtemps. Et désormais, je ne veux plus approcher cet objectif uniquement avec les anciennes méthodes. Je veux essayer de le faire en reliant dans un même flux des outils de production dopés à l'IA, une pipeline de génération de contenu, un backend exploitable et même une expérience temps réel. Omnilude se situe exactement sur cette trajectoire.
Autrement dit, ce projet est à la fois une expérimentation d'agents IA visant à exploiter au maximum l'IA, et un projet de développement produit qui doit, à terme, converger vers un service réellement publiable.
Plusieurs services, mais vus comme un seul backend produit
Au moment où j'écris cet article, le backend d'Omnilude reste encore un seul service monolithique. Mais je ne vois pas ce monolithe comme un simple gros serveur. Je le vois comme une structure à l'intérieur de laquelle coexistent plusieurs frontières de responsabilité. Les auth-service, ai-service, blog-service, game-service, storage-service, backbone-service, mmorpg-service, ainsi que la base commune common(jspring) mentionnés dans cet article ne correspondent pas tant aux unités de déploiement actuelles qu'à la manière dont je comprends et fais évoluer ce système. Dans ce texte, j'appellerai simplement cette couche interne partagée common.
Ainsi, lorsque je dis plusieurs services, mais vus comme un seul backend produit, je ne cherche pas à exagérer la structure actuelle. C'est plutôt une façon d'expliquer quelles responsabilités sont déjà séparées à l'intérieur d'un système encore attaché d'un seul bloc, et dans quelle direction ces frontières peuvent devenir plus nettes. Ce qui compte n'est pas le nombre de services en soi, mais le fait que chaque responsabilité soit reliée à un même produit à partir d'un ensemble commun de conventions.
Plutôt que de voir l'Omnilude actuel comme un unique serveur géant, je le vois comme une structure où plusieurs frontières coexistent à l'intérieur d'un même backend produit. Je veux raconter, dans un autre article, le moment où ces frontières seront réellement séparées en dépôts et en unités de déploiement. Dans celui-ci, je veux déployer avec le vocabulaire actuel les séparations de responsabilité déjà présentes à l'intérieur du monolithe.
Le schéma ci-dessous donne une vue d'ensemble du backend non legacy actuel, regroupé et observé comme un seul backend produit.
Les trois points que je considère comme les plus importants dans ce schéma sont les suivants.
Premièrement, common constitue le socle de l'ensemble. Chaque service existe de manière indépendante, mais les conventions d'authentification, les schémas d'appels HTTP internes, les chemins de publication d'événements, la gestion du stockage et le traitement des jobs distribués se répètent tous sur une base commune. C'est ce qui empêche la structure de se disperser.
Deuxièmement, les domaines produit et l'infrastructure opérationnelle sont séparés. auth, ai, game, mmorpg et blog représentent les capacités du produit, tandis que storage et backbone prennent en charge le socle opérationnel commun dont ces capacités ont besoin pour fonctionner dans le réel.
Troisièmement, cette séparation rend aussi la structure plus adaptée à l'IA. Les coding agents ne gèrent pas bien une base de code gigantesque lorsqu'il n'y a pas de frontière de contexte claire. À l'inverse, ils fonctionnent beaucoup plus stablement dans une structure où les rôles sont séparés et les conventions explicites. Je conçois cette structure non seulement pour qu'elle soit plus facile à maintenir par des humains, mais aussi pour offrir un meilleur environnement de travail à l'IA.
common est au centre de cette structure
La première chose qu'il faut expliquer pour comprendre ce projet, c'est common, parce que c'est finalement ce qui permet à l'ensemble du système de bouger comme un seul produit.
common n'est pas un simple ensemble d'utilitaires. Je le vois plutôt comme une sorte de SDK de plateforme interne. Si l'on observe concrètement la manière dont les services se connectent entre eux, on voit qu'une grande partie est déjà standardisée dans common.
common-corefournit la base la plus basse, avec JWT, exceptions partagées et utilitaires de base.common-webfournit le client HTTP interne et les conventions communes de la couche web.common-dataregroupe JPA, QueryDSL et le socle commun d'accès aux données.common-messagingunifie la voie de publication des événements.common-cloudstandardise les intégrations de traitement de fichiers, comme les storage commits et les access URLs.common-dtefournit le flux des jobs distribués, des workflows et des job events.
La raison pour laquelle je pense que cette couche est importante est simple. Dans un développement AI-first, ce qui compte davantage que la vitesse d'écriture du code, c'est la quantité de motifs répétables déjà présents dans le système. Si chaque nouvelle fonctionnalité oblige un humain ou un agent à comprendre une structure entièrement différente, la productivité chute très vite. À l'inverse, si la base commune est solide, le problème peut être découpé en unités plus petites, et les points de revue deviennent bien plus clairs.
Par exemple, lorsque game-service appelle une fonctionnalité IA, il ne se branche pas directement sur des modèles externes sans structure. Il appelle ai-service en interne. Le stockage de fichiers est lui aussi organisé pour éviter que chaque service manipule S3 de son côté, en passant plutôt par storage-service. Les événements, lorsque c'est nécessaire, circulent autour de backbone-service. Grâce à cette cohérence, je peux donner des instructions plus précises à l'IA et valider les résultats beaucoup plus vite.
Cette structure est également importante du point de vue du développeur. Même si l'on a l'impression qu'il y a beaucoup de services, ils partagent en réalité le même langage, le même framework, les mêmes conventions et la même plateforme interne. L'unité de pensée ne se fragmente donc pas complètement. C'est, à mes yeux, la plus grande force de la structure d'Omnilude.
Pourquoi les domaines produit sont séparés de cette manière
Je veux maintenant expliquer un peu plus concrètement le rôle de chaque service. Plus que l'énumération des noms, ce qui importe, c'est la logique de cette séparation.
auth-service : le point de départ de la confiance
auth-service gère la connexion, les comptes, les appareils, les permissions, les crédits et les journaux d'activité. En surface, il peut ressembler à un serveur d'authentification assez classique, mais je ne le considère pas comme une simple API de login. Ce service définit la frontière de confiance de l'ensemble du produit.
Savoir qui est l'utilisateur, quelle session il possède, quels droits il détient et quels crédits ou historiques d'usage lui sont associés, tout cela commence en grande partie ici. Si les autres services doivent expérimenter et ajouter des fonctionnalités plus rapidement, cette couche de confiance fondamentale doit justement être plus prudente et plus explicite.
Ce qui est intéressant, c'est que ce service n'est pas complètement isolé. Au démarrage, il synchronise les paramètres de plateforme avec backbone-service et contribue à former la convention JWT partagée de tout le backend. Autrement dit, l'authentification est à la fois une capacité indépendante et un langage commun qui relie tout le système.
ai-service : le hub d'exécution de l'IA
ai-service est le service le plus emblématique de ce projet. Les appels LLM, les workflows multi-agents, la génération de médias, les agents produit, le roleplay et diverses API d'IA orientées système s'y concentrent.
Le point important est que ce service n'est pas seulement un proxy pour des API de modèles externes. Je le vois comme un hub d'exécution de l'IA. C'est la couche centrale qui décide quel modèle appeler, quel workflow suivre, quels événements émettre, quels résultats stocker et quelle progression diffuser vers l'extérieur.
Je pense que cette structure deviendra encore plus importante à l'avenir. Dans les projets qui utilisent fortement l'IA, dès que les appels aux modèles externes commencent à se disperser partout, ils deviennent très vite difficiles à contrôler. Si l'on prend en compte les coûts, la gestion des échecs, les conventions de prompt, les journaux d'exécution, le stockage des résultats et la stratégie de remplacement des fournisseurs, il est préférable que l'IA soit coordonnée à un seul endroit. C'est précisément le rôle que prend ai-service.
game-service : le domaine que je veux concrétiser en premier
game-service est le domaine produit central de ce projet. La génération de scénarios, le story quiz, les sessions de jeu, les métadonnées partagées du jeu et les capacités d'asset studio y sont réunis.
La raison pour laquelle ce service est important est simple. Ce que je veux lancer en premier dans Omnilude finit par rejoindre ce domaine. C'est pourquoi ce service n'est pas conçu comme un simple entrepôt de données, mais comme un domaine qui interagit avec l'IA plus que n'importe quel autre. La création de scénarios, la génération d'assets, la création de sessions et l'orchestration du flux de jeu convergent ici.
Ce qui compte tout particulièrement, c'est que game-service est directement relié à ai-service. La génération de contenu, la relecture, les tâches de soutien, la production d'assets et certains flux d'automatisation vont inévitablement être accélérés par l'IA. Plutôt que de cacher ce lien, j'ai préféré le rendre visible dans la structure même.
mmorpg-service : le runtime temps réel comme axe séparé
mmorpg-service appartient au même univers de jeu, mais sa nature est très différente. Ce service ne fournit pas seulement des API de gestion basées sur HTTP. Il exploite également un runtime WebSocket en temps réel sur un port de gateway distinct.
Si l'on me demande pourquoi je ne l'ai pas mélangé au même service, la réponse est claire. Les API de contenu centrées sur le CRUD et les serveurs de jeu temps réel ont des modes d'échec, des exigences de performance et des logiques de gestion d'état complètement différents. Je pense qu'il est plus juste de ne pas les traiter dans la même unité de pensée.
Le mode d'authentification est lui aussi intéressant. Au moment de la connexion, la convention JWT émise par auth-service est validée localement dans la gateway de mmorpg-service. Autrement dit, le runtime ne rappelle pas le service d'authentification en HTTP à chaque fois. Cette structure montre bien à quel endroit un système temps réel doit se distinguer d'une API métier classique.
blog-service : la mémoire externe du projet
blog-service peut sembler relativement moins complexe, mais il occupe pour moi une place importante. Ce n'est pas seulement un CMS destiné à faire fonctionner un blog. C'est le canal par lequel j'accumule vers l'extérieur ce que je construis, les décisions que je prends et les expérimentations que je mène.
Si Omnilude ne doit pas rester un projet tournant uniquement en interne, alors sa structure, ses expérimentations et ses tâtonnements doivent pouvoir être expliqués vers l'extérieur. blog-service est responsable de cette mémoire. C'est aussi pour cela que les posts, les commentaires, les traductions, le SEO et les liens vers les assets statiques se retrouvent ici. Le produit a besoin d'une couche de communication externe.
Pourquoi l'infrastructure opérationnelle est séparée
Un service ne fonctionne pas uniquement grâce à des capacités produit. Il faut nécessairement une couche capable de stocker les résultats, de gérer le cycle de vie des fichiers, de diffuser la progression, de transporter les événements, de suivre les travaux longs et de maintenir les connexions temps réel.
Dans Omnilude, ce rôle est principalement assuré par storage-service et backbone-service.
storage-service : la véritable durée de vie des fichiers est gérée ici
Dans beaucoup de projets, le stockage de fichiers est traité comme une capacité ajoutée plus tard. Mais dans un vrai produit, l'upload, l'état temporaire, commit/cancel, l'access URL, la thumbnail URL et les métadonnées des objets statiques ont tous de l'importance. Les fichiers ne sont pas simplement déposés puis oubliés. Ils ont un cycle de vie.
C'est pour cette raison que j'ai séparé storage-service. Qu'il s'agisse d'images de blog, de résultats générés par l'IA ou d'assets de jeu, j'ai essayé de faire converger tout cela vers cette même voie. Ainsi, quel que soit le service qui manipule un fichier, il peut le faire selon les mêmes règles. Et si la politique d'accès ou la stratégie de stockage change plus tard, l'impact reste plus facile à contenir.
backbone-service : le centre de l'opérationnel et de l'asynchrone
backbone-service, comme son nom l'indique, se rapproche de la véritable colonne vertébrale. Le hub d'événements, le SSE, le WebSocket, les paramètres de plateforme et le flux d'événements des DTE Jobs y sont concentrés.
Ce service est particulièrement important parce qu'il s'emboîte très bien avec l'exécution de l'IA. Beaucoup des fonctionnalités que je construis ne s'arrêtent pas à une simple API synchrone. Lorsqu'un utilisateur lance une tâche, il faut des points d'avancement intermédiaires, un événement de fin et, dans certains cas, une connexion temps réel ou une notification. Si chaque service métier commence à résoudre ces besoins séparément, la structure se dégrade très vite.
C'est pourquoi j'ai rassemblé la livraison asynchrone et la couche temps réel opérationnelle dans backbone. Je considère comme plus gérable une approche où le chemin par défaut des événements est organisé sous la forme HTTP -> backbone -> Kafka/RabbitMQ, tandis que la progression liée au DTE passe par SSE.
Le flux ci-dessous simplifie la manière dont une tâche de génération par IA s'enchaîne en pratique.
La raison pour laquelle j'apprécie cette structure est claire. ai-service porte la génération, storage-service porte le stockage des résultats et backbone-service porte la diffusion externe de la progression. Comme les responsabilités sont séparées, chaque partie peut être validée bien plus nettement.
Voici comment j'utilise les couches de données et de messagerie
Pour voir la structure de manière plus complète, il faut aussi regarder les couches de stockage et de messagerie. Le backend non legacy actuel d'Omnilude ne mélange pas l'infrastructure de manière désordonnée. Le rôle de chaque couche est assez clair.
- PostgreSQL est le stockage persistant par défaut pour presque tous les services.
auth,ai,blog,game,storage,backboneetmmorpgpartagent tous cet axe. - Redis est utilisé moins comme simple cache que pour le Pub/Sub et la livraison d'événements orientés session. Il est largement présent, mais son usage reste très stratégique.
- Cassandra est aujourd'hui plus proche de la couche de journaux d'activité et d'écriture d'événements côté
auth-serviceetbackbone-service. Une frontière existe pour éviter que tous les services ne s'y branchent indistinctement. - Kafka et RabbitMQ sont, dans les faits, gérés avec
backbone-serviceau centre. Au lieu que chaque service se connecte directement à un broker de messages, ils envoient d'abord leurs événements à backbone, puis backbone les propage vers la couche de messagerie externe. - S3 est géré directement par
storage-service, et les autres services y accèdent autant que possible à travers lui. C'est un choix destiné à concentrer en un seul endroit les règles liées au traitement des fichiers.
À première vue, cela peut sembler un peu indirect. Pourtant, je pense que ce type de séparation compte beaucoup plus à long terme. Dès que les services fonctionnels commencent à assumer eux-mêmes tous les chemins de stockage et de messagerie, les frontières de responsabilité deviennent rapidement floues. À l'inverse, si l'accès à l'infrastructure est organisé à travers des services jouant un rôle de gateway, comme aujourd'hui, il devient bien plus facile ensuite de changer les politiques d'exploitation ou de réduire l'étendue d'un point de défaillance.
Cette structure présente aussi un avantage important lorsqu'on travaille avec l'IA. Si l'on sait clairement jusqu'où une fonctionnalité traite les choses directement et à partir de quel moment elle doit passer par un chemin partagé, les résultats obtenus d'un agent deviennent beaucoup plus stables.
Pourquoi les requêtes authentifiées et les connexions temps réel suivent des chemins différents
Pour comprendre plus concrètement la structure d'Omnilude, il vaut mieux regarder séparément les requêtes synchrones et les connexions temps réel.
Les consultations de contenu et les tâches de gestion suivent, pour la plupart, un flux dans lequel l'utilisateur se connecte via auth-service, puis appelle une API métier. Dans ce cas, ce qui compte, c'est que la convention JWT partagée et le modèle de permissions soient préservés. Comme blog-service, game-service et ai-service partagent ces conventions, l'utilisateur bénéficie d'une expérience relativement cohérente même si les services sont séparés.
Les connexions temps réel ont, elles, des exigences différentes. mmorpg-service, en particulier, utilise un port de gateway distinct sur :9088/ws. L'utilisateur possède déjà un token émis par auth-service, et la gateway le valide localement. Cette structure est conçue pour établir rapidement une session sans aller-retour vers le serveur d'authentification à chaque fois.
Le flux ci-dessous montre cette différence.
Cette différence compte davantage qu'il n'y paraît. Avec cette structure, je voulais rendre explicite le fait que les API métier classiques, les travaux IA de longue durée et les sessions de jeu temps réel doivent chacun avoir leur propre centre de gravité. Uniformiser absolument tout peut sembler simple en apparence, mais en exploitation réelle cela aggrave souvent les problèmes.
Comment cette structure se relie au développement AI-first
À ce stade, une question peut surgir. Pourquoi une structure de ce type est-elle liée au fait de bien utiliser l'IA ?
Je pense que cette question est essentielle, parce que beaucoup de développeurs voient encore l'IA comme un simple outil qui écrit du code à leur place. Pourtant, le moment où la productivité fait réellement un saut n'est pas celui où l'on génère simplement plus de code. C'est celui où le système commence à adopter une structure que l'IA peut comprendre plus facilement.
Par exemple :
- Un ensemble de services aux responsabilités séparées se prête mieux au découpage des prompts qu'un service unique gigantesque aux rôles flous.
- Une structure dotée d'une plateforme interne comme
commonfournit aux agents un contexte bien plus stable qu'une base de code sans conventions partagées. - Lorsque la gestion des fichiers, celle des événements, l'exécution de l'IA et les connexions temps réel sont séparées, il devient plus simple de localiser la frontière où survient un problème.
- Les points qu'un humain doit relire à la fin deviennent eux aussi beaucoup plus nets.
En définitive, le développement AI-first ne consiste pas à ajouter davantage d'IA partout. Il s'agit plutôt d'organiser le système pour que l'IA puisse travailler de manière plus cohérente. C'est précisément ce point que j'expérimente en construisant Omnilude.
Dans cette perspective, je pense que ce dont les développeurs ont besoin aujourd'hui, c'est davantage d'entraînement que d'actualités sur les modèles. Pas seulement un entraînement à écrire de meilleurs prompts, mais aussi à construire de meilleures structures, à relire avec de meilleurs critères et à concevoir le contexte dans lequel l'IA peut travailler. Je considère que ce projet doit, au final, devenir le résultat de cet entraînement.
La destination reste le lancement d'Omnilude
Il y a une raison pour laquelle je ne voulais pas que cet article s'arrête à une simple présentation d'architecture. Cette structure n'existe pas pour être expliquée. Elle existe pour construire quelque chose qui puisse, au bout du compte, être réellement lancé.
Ce que je fais aujourd'hui consiste à construire des outils de production fondés sur l'IA et à implémenter par-dessus une plateforme de jeu. Ce backend est conçu pour que ces deux flux se rencontrent. L'IA pousse la production de contenu, le domaine du jeu transforme cela en expérience produit, et l'infrastructure opérationnelle élève l'ensemble à un niveau capable de soutenir un véritable service.
Je veux toujours lancer un jeu bien réalisé. Et je veux vérifier par moi-même si ce processus peut devenir possible non seulement grâce à l'intuition et à l'enthousiasme, mais aussi grâce à une meilleure structure, à une expérimentation plus rapide et à une validation plus précise. Omnilude est, pour moi, le projet dans lequel se concentrent ces attentes.
C'est pourquoi, dans ce blog, je ne compte pas parler uniquement d'architecture. Je veux continuer à documenter les fonctionnalités qui arrivent réellement, jusqu'où l'IA pousse les choses, à quel endroit le jugement humain doit intervenir, et si cette structure conduit réellement à un produit que l'on peut lancer.
À l'heure actuelle, je présente la structure du backend, mais la question la plus importante dans mon esprit reste la suivante : cette manière de faire peut-elle vraiment aller jusqu'au bout ?
Je ne veux pas répondre à cette question uniquement avec des mots. Je veux le vérifier en construisant réellement Omnilude.