Aller au contenu principal
Architecture IA

Architecture modulaire en silos : les contrats d'interface entre modules

Pourquoi l'IA casse ton code à mesure qu'il grossit, et comment des modules en silos avec contrats explicites règlent le problème à la racine.

Par Xavier Agapé10 min de lecture

Au début, l’agent est magique. Il modifie une fonction, tout marche. À trois mille lignes, il commence à casser des choses ailleurs. À dix mille, chaque modification devient une loterie : tu corriges un bug, deux autres apparaissent dans des fichiers que tu n’as pas touchés.

Le réflexe est d’accuser le modèle. Le vrai coupable est ailleurs : ton code n’a pas de frontières, et un agent ne peut respecter que des frontières qui existent.

TL;DR

  • Un agent ne casse pas ce qu’il ne peut pas atteindre. L’architecture modulaire n’est pas un confort esthétique, c’est un mécanisme de limitation des dégâts.
  • Un module est un silo : il possède ses données, expose un contrat, et ne laisse rien fuiter de son fonctionnement interne.
  • L’analogie de la mairie : tu vas au guichet, tu remplis un formulaire, tu obtiens un document. Tu ne rentres jamais dans le bureau de l’agent pour trier ses dossiers toi-même.
  • La contrepartie est de faire moins, pas plus : chaque fonctionnalité ajoutée traverse des modules et augmente le couplage.

Pourquoi l’IA amplifie les problèmes d’architecture

Un développeur humain qui travaille sur une base mal découpée est ralenti. Il compense avec sa mémoire du projet, son intuition sur les effets de bord, sa connaissance des zones fragiles.

Un agent n’a rien de tout ça. Il a un contexte (une fenêtre de texte) et ce qu’il y trouve. Trois conséquences directes :

  1. Il ne voit pas ce qu’il ne lit pas. Si une fonction est utilisée à six endroits et qu’il n’en a lu qu’un, il modifie la signature et casse les cinq autres, sans mauvaise intention.
  2. Il n’a pas de mémoire du projet entre les sessions. Ce que tu lui as expliqué hier n’existe plus aujourd’hui, sauf s’il est écrit dans le code ou dans un fichier qu’il relit.
  3. Il extrapole ce qui manque. Face à une zone qu’il ne comprend pas, il produit ce qui semble le plus plausible, ce qui est exactement la définition d’un modèle probabiliste.

Conclusion pratique : ce qui protège ton code, c’est le fait qu’une modification dans un module ne puisse pas, structurellement, affecter un autre module. Pas une consigne dans le prompt.

Qu’est-ce qu’un silo, concrètement ?

Un module en silo respecte trois règles, et elles sont indissociables.

Règle 1 : Il possède ses données

Un module est propriétaire de ses tables, ses fichiers, son état. Personne d’autre n’y touche directement. Si le module « facturation » possède la table des factures, aucun autre module ne l’interroge, il passe par la facturation.

C’est la règle la plus violée, et celle qui produit le plus de dégâts : quand cinq modules lisent la même table, changer une colonne devient impossible, et l’agent ne peut pas savoir qui casse.

Règle 2 : Il expose un contrat, pas son intérieur

Le module publie une liste courte de fonctions : ce qu’on peut lui demander, ce qu’il renvoie. Tout le reste (comment il stocke, comment il calcule, quelles bibliothèques il utilise) est privé et peut changer sans prévenir.

C’est ce que les systèmes de modules formalisent avec la notion d’export : ce qui n’est pas explicitement exposé n’est pas accessible depuis l’extérieur, la documentation TypeScript sur les modules décrit précisément ce mécanisme. La frontière n’est alors pas une convention d’équipe : c’est une erreur de compilation.

Règle 3 : Les dépendances vont dans un seul sens

Si le module A appelle le module B, alors B n’appelle jamais A. Une dépendance circulaire transforme deux modules en un seul gros bloc, avec la complexité en plus.

L’analogie de la mairie

C’est l’image qui fait comprendre les contrats d’interface en trente secondes, y compris à quelqu’un qui ne code pas.

Tu as besoin d’un acte de naissance. Tu vas à la mairie. Tu te présentes au guichet, tu remplis un formulaire avec des champs précis, et on te remet un document conforme.

Ce que tu ne fais pas : entrer dans les bureaux, ouvrir les armoires, trier les dossiers toi-même, ou décider de réorganiser leur classement parce que ça t’arrangerait.

Transposé au code :

À la mairie Dans ton code
Le guichet L’interface publique du module
Le formulaire La signature de la fonction (ce que tu dois fournir)
Le document remis Le type de retour (ce que tu obtiens)
Les bureaux et les armoires L’implémentation interne, privée
Réorganiser le classement Modifier directement les données d’un autre module

Pourquoi cette image fonctionne si bien avec un agent : elle rend l’interdit évident. Un agent à qui tu dis « passe par le guichet du module facturation » comprend immédiatement qu’il ne doit pas aller écrire dans la table des factures. Et si l’architecture est correcte, il n’en a de toute façon pas la possibilité.

Ce que ça change concrètement quand tu travailles avec un agent

Le contexte devient petit. Pour modifier le module facturation, l’agent a besoin du module facturation et des contrats des modules voisins, pas de leur code. Un contexte réduit, c’est moins de dérive, moins de coût, et une meilleure qualité de réponse.

Le périmètre de casse est borné. Une erreur dans un module reste dans ce module. C’est ce qui rend possible de laisser un agent travailler avec un degré d’autonomie raisonnable : le pire cas est connu.

Le travail en parallèle devient possible. Deux agents peuvent travailler sur deux modules distincts sans se marcher dessus, sujet du prochain article de cette série.

La revue devient tenable. Tu relis les changements d’un module, pas d’une base entière. Et si le contrat public n’a pas bougé, tu sais que rien d’externe n’est affecté.

Le lien avec DRY et KISS : attention au piège

L’architecture modulaire entre parfois en tension apparente avec le principe DRY. Deux modules ont besoin d’une fonction similaire : faut-il la factoriser dans un module partagé ?

Souvent non. Une factorisation prématurée crée une dépendance entre deux modules qui n’avaient aucune raison de se connaître. Le jour où l’un des deux a besoin d’une variante, tu ajoutes un paramètre, puis un autre, et la fonction « partagée » devient un nœud que personne n’ose toucher.

La règle qui fonctionne : DRY s’applique à l’intérieur d’un module sans réserve. Entre modules, une duplication limitée est souvent préférable à un couplage. Deux fonctions de dix lignes qui se ressemblent coûtent moins cher qu’une dépendance croisée.

Cette préférence pour la solution la plus simple rejoint directement le principe KISS appliqué au code généré par IA : le code le plus maintenable est celui qui a le moins de raisons de changer.

La contrepartie : faire moins de fonctionnalités

C’est le point qui surprend le plus, et c’est pourtant celui qui décide de la tenue du projet dans la durée.

Chaque fonctionnalité ajoutée traverse des modules. Elle crée des liens, des cas particuliers, des dépendances. Un produit avec deux fois plus de fonctionnalités n’est pas deux fois plus complexe : il l’est beaucoup plus, parce que ce sont les interactions qui explosent.

Quand on développe avec de l’IA, la tentation inverse est énorme : ajouter une fonctionnalité coûte quelques minutes. Le coût n’est pas dans l’écriture, il est dans tout ce qui suit, la maintenance, les régressions, le contexte à charger, la revue.

Le réflexe à prendre : avant d’ajouter, se demander quel module la porte. Si la réponse est « un peu partout », la fonctionnalité n’est pas prête à être développée : c’est l’architecture qu’il faut clarifier d’abord.

Comment découper une base existante

Tu n’as pas besoin de tout réécrire. La démarche progressive qui fonctionne :

  1. Liste les domaines métier, pas les couches techniques. « Facturation », « utilisateurs », « notifications », pas « contrôleurs », « services », « utilitaires ».
  2. Attribue chaque table à un seul domaine. L’exercice révèle immédiatement les zones de couplage : une table réclamée par trois domaines est le nœud du problème.
  3. Crée le contrat avant de déplacer le code. Écris l’interface publique du module (les fonctions exposées et leurs types) avant tout déplacement de fichier.
  4. Migre les appels un par un, en passant par le contrat. La duplication temporaire pendant la transition est normale.
  5. Verrouille avec un hook. Une règle qui refuse tout accès direct aux données d’un autre module transforme la convention en garantie : c’est exactement l’usage décrit dans l’article sur les hooks et le déterminisme d’un agent.

Checklist : découper en modules exploitables par un agent

  • Chaque table ou fichier de données appartient à un seul module.
  • Aucun module ne lit directement les données d’un autre.
  • Chaque module expose une interface publique courte et explicite.
  • Ce qui n’est pas exposé n’est pas accessible depuis l’extérieur.
  • Les dépendances entre modules vont dans un seul sens, sans cycle.
  • Un agent peut travailler sur un module en ne lisant que ce module et les contrats voisins.
  • La factorisation entre modules est refusée par défaut, acceptée sur justification.
  • Une nouvelle fonctionnalité est rattachée à un module identifié avant d’être écrite.

FAQ

Quelle taille doit avoir un module ?

Le bon critère n’est pas le nombre de lignes mais la cohérence : un module correspond à un domaine métier que tu peux décrire en une phrase sans utiliser « et ». Si tu dis « ce module gère la facturation et les notifications », il y a deux modules. En pratique, un module qu’un agent peut charger entièrement dans son contexte avec ses tests est de bonne taille.

Faut-il découper dès le début d’un projet ?

Dès qu’il y a plus d’un domaine métier, oui, le coût est quasi nul au démarrage et considérable ensuite. En revanche, ne crée pas dix modules pour un prototype de deux écrans : la sur-modularisation ajoute de l’indirection sans bénéfice. Un bon signal pour découper : quand tu commences à hésiter sur l’endroit où ranger un nouveau fichier.

Comment empêcher un agent de contourner les frontières ?

Par la mécanique, pas par la consigne. Trois niveaux, du plus faible au plus fort : une convention écrite dans les règles du projet (l’agent l’oubliera à mesure que le contexte s’allonge), une règle de compilation ou de lint qui échoue en cas d’import interdit, et un hook qui refuse l’écriture hors du module courant. Les deux derniers tiennent, le premier non.

Que faire d’une donnée réellement partagée par plusieurs modules ?

Elle appartient probablement à un module qui n’existe pas encore. L’utilisateur est le cas typique : partagé par tout le monde, il mérite son propre module qui expose les opérations autorisées (récupérer un profil, vérifier un droit) plutôt qu’un accès libre à la table. Si aucun module ne peut légitimement la posséder, c’est souvent le signe que le découpage métier doit être revu.

Conclusion

Une architecture modulaire n’est pas un raffinement de puriste : c’est ce qui détermine si tu peux, ou non, confier du travail à un agent sans le surveiller ligne à ligne.

Un module qui possède ses données, expose un contrat court et ne dépend que dans un sens est une zone où l’erreur reste confinée. C’est le seul moyen connu de faire grossir une base de code générée par IA sans que chaque modification devienne un pari.

Commence par l’exercice le plus simple : liste tes tables et attribue chacune à un seul domaine. Les conflits que tu vas trouver sont, très exactement, la liste de tes futurs bugs.

Cette architecture est expliquée en direct, schéma à l’appui, dans le live sur l’invitation par code et l’architecture modulaire.

À lire aussi