Le refactor à 18 mois
L'app marche, elle grossit, et chaque nouvelle feature coûte plus cher que la précédente. Le moment où il faudrait accélérer est celui où on réécrit.
Architecture d'app — le layou-dev-kit
Une architecture qui tient sur de la bonne volonté ne tient pas. Celle-ci est découpée en modules dont les dépendances sont vérifiées par le compilateur. Si presentation importe data, ce n'est pas un warning, ni un lint, ni une remarque en revue : c'est une erreur rouge qui bloque le build.
Les 5 modules, et ce que chacun a le droit de voir
Le coût du sans-archi
L'app marche, elle grossit, et chaque nouvelle feature coûte plus cher que la précédente. Le moment où il faudrait accélérer est celui où on réécrit.
Une seule personne sait pourquoi ce fichier est là. La connaissance est dans une tête, pas dans la structure.
On discute d'indentation et de nommage pendant qu'un appel à la base se glisse dans un composant.
Les fondations
Tout le monde cite ces principes. La question n'a jamais été de les connaître : c'est de savoir ce qui les fait tenir un mardi soir, sur une feature en retard, quand personne n'a le temps de relire. Pour chacun, voilà le mécanisme qui l'applique sans dépendre de la bonne volonté de l'équipe.
Un fichier, une raison de changer.
Une seule classe publique par fichier, 50 lignes max par fonction, 200 par classe. Au-delà, le lint casse le build : on ne discute pas en revue, on découpe.
Ouvert à l'extension, fermé à la modification.
Une seconde implémentation s'ajoute à côté de la première — SqlUserRepository à côté de RestUserRepository — sans renommer ni toucher un seul appelant. Le nom porte la techno, pas le fait d'implémenter.
Une implémentation doit pouvoir en remplacer une autre sans surprise.
Chaque interface a son Fake…, injecté à la place de la vraie en test. Un seul préfixe de test est autorisé : pas de Mock, Stub et InMemory côte à côte qui laissent croire à trois contrats différents.
Un contrat par rôle métier, pas un contrat fourre-tout.
Une interface par feature — AuthRepository, OrderRepository — déclarée dans domain. Et les lectures ne traversent pas un cas d'usage : on ne fabrique pas une couche pour le plaisir de la symétrie.
Le métier ne dépend de rien. Tout le reste dépend du métier.
C'est la matrice plus bas. domain déclare l'interface et n'importe personne, data l'implémente, app est le seul endroit où les deux se rencontrent. Une flèche à l'envers ne compile pas.
La solution la plus simple qui tient.
80 % des services font cinq lignes : une classe, une injection. La cérémonie complète — interface, fake, erreurs typées — est réservée aux 20 % où elle paye : paiement, authentification, ce qui se teste en isolation.
On n'écrit pas pour un besoin qu'on n'a pas.
Une interface ne s'extrait pas « au cas où » : elle s'extrait le jour où un test a besoin d'un fake. Pré-abstraire, c'est prendre de la dette pour un futur qui n'arrive pas.
Chaque couche a un seul sujet.
Un composant feuille ne connaît que ses paramètres : zéro accès au gestionnaire d'état, vérifié par le lint. Un seul point de l'arbre d'UI branche l'état — ailleurs, c'est une fuite.
On assemble des petites pièces, on n'empile pas des classes.
Plusieurs petits états qui s'observent, plutôt qu'un objet-dieu de 800 lignes. Même règle côté UI : un composant qui en contient un autre est un organisme, pas une feuille.
Un principe manque volontairement à cette liste : DRY. Il s'arrête à la frontière data, où le DTO duplique la forme de l'entité — exprès. C'est ce doublon qui fait qu'un champ renommé côté base casse un mapper, et pas quinze écrans.
Ce que ça rapporte
Une promesse d'architecture sans mécanisme, c'est du slogan. Pour chacune, voilà ce qui l'applique concrètement.
Ajouter du monde sans ralentir l'équipe
Le monorepo est découpé par couche, pas par écran. Deux devs sur deux features ne se marchent pas dessus : ils ne touchent pas les mêmes modules.
Le rayon d'impact d'un changement est connu d'avance
Quand une règle métier bouge, elle bouge dans domain. Rien d'autre ne compile différemment. C'est ça, une archi tenue : savoir ce qu'on ne casse pas.
domain est pur : testable sans émulateur, sans base de données, sans composant d'UIdataLe MVP et la V3 sont le même code
Les couches sont posées dès le jour 1, même pour un MVP. Pas de code jetable, donc pas de réécriture au moment où l'app commence enfin à marcher.
Either / Result) : le chemin d'erreur est dans la signature, pas dans un try/catch oubliéPeu de points de passage, donc peu de choses à auditer
Une architecture ne sécurise pas une app — elle décide du nombre d'endroits par lesquels il faut passer. C'est ce nombre qui rend un audit faisable.
.env de production. Côté client, ils sont injectés à la compilationdataLa règle
Chaque module déclare ses dépendances. Ce tableau n'est pas une convention d'équipe affichée sur un mur : c'est l'état réel des manifestes. Une case rouge, c'est un build cassé.
| Le module… | core | domain | data | presentation | app |
|---|---|---|---|---|---|
| core | · | ✕ | ✕ | ✕ | ✕ |
| domain | ✓ | · | ✕ | ✕ | ✕ |
| data | ✓ | ✓ | · | ✕ | ✕ |
| presentation | ✓ | ✓ | ✕ | · | ✕ |
| app | ✓ | ✓ | ✓ | ✓ | · |
// ✅ OK — presentation → domain
import app/domain/auth/entities/app_user
// ❌ ERREUR DE COMPILATION — data qui fuite dans presentation
import app/data/auth/datasources/auth_remote_datasource L'outillage
Ce que le compilateur ne peut pas voir passe par des règles de lint maison. Ce sont des règles qu'on n'a plus à défendre en revue de code — elles échouent toutes seules.
Et il n'y a pas d'échappatoire : désactiver une règle avec un commentaire ignore est interdit par le kit. On refactorise.
Avant / après
| Sans le dev-kit | Avec | |
|---|---|---|
| Import interdit | Ça compile (mais c'est cassé) | Erreur de compilation |
| Composant trop gros | Personne ne le voit | Lint error à 200 lignes |
| Composant feuille branché sur l'état | Ça marche | Lint error |
| Test d'un composant | Mock hell | Zéro mock |
| Nouvel agent IA | Doit relire toutes les conventions | Le compilateur le guide |
Pourquoi ça compte en 2026
Un agent IA écrit vite, et il triche : une source de données importée dans un composant, trois classes dans un fichier, un état local « parce que c'est plus simple ». Le pire, c'est que ça compile — et que ça pourrit le codebase en silence. Ce qui l'arrête n'est pas un prompt plus long : c'est un projet où le raccourci ne compile pas. L'agent reçoit l'erreur de build, comprend la contrainte, et se corrige sans intervention. Le compilateur est le relecteur qui ne dort jamais et ne perd pas son contexte.
Nous n'avons pas besoin de faire confiance à l'IA. L'architecture elle-même est le garde-fou.Voir la boucle de livraison complète →
Honnêtement
Compter 2 à 3 jours de scaffolding du monorepo et des modules sur le premier projet, et environ une journée pour écrire les règles de lint avec leurs tests. Ensuite c'est réutilisable d'un projet à l'autre.
Un hackathon, un prototype qu'on jette dans deux semaines : les 4 couches coûtent plus qu'elles ne rapportent. Ça devient rentable quand le code doit survivre à son auteur.
Le dev-kit ne contient ni règles d'accès à la base, ni politique de chiffrement, ni conformité RGPD. Il réduit le nombre de points de passage — l'audit et les règles restent un travail à part entière.
En détail
Les mêmes 9 vues — modules, couches, flux de données, atomic design, injection, erreurs, nommage, UI, services — déclinées sur trois stacks : Flutter/Firebase, React Native/Supabase, TanStack/Convex. Les onglets en haut changent de stack, l'architecture ne change pas. Navigation au clavier (1-9, Échap), ou Auto Tour.
Ouvrir en plein écran ↗Reprise d'un existant ou démarrage from-scratch : nous regardons ce que coûterait de tenir cette archi chez vous, et ce que ça vous éviterait.