Architecture backend · Travail d'équipe · Juillet 2026, puis itéré
Transformer un BFF monolithique en tranches détenues par les features sans changer un contrat
Pendant une migration incrémentale vers React, le backend-for-frontend ASP.NET Core était le point d'intégration entre une application web multi-tenant et son API interne — et son câblage HTTP, son orchestration, ses contrats et son infrastructure de tests étaient concentrés dans quelques gros fichiers partagés que chaque développeur devait toucher. J'ai mené le refactor vers des endpoints, services, contrats et tests détenus par les features, protégé par des snapshots Swagger à chaque étape : 307 opérations préservées, zéro handler restant dans la composition root.
- Mon rôle
- Architecture, séquence d'extraction, modèle de composition, contrôles de compatibilité et sécurité de release : les miens. La logique des features déplacées : celle de leurs auteurs.
- Stack
- ASP.NET Core
- C#
- Endpoint routing
- Dependency injection
- Swagger / OpenAPI
- NUnit
En bref
- Fichier central d'endpoints de 5 657 à 81 lignes ; de 128 enregistrements à 0 handler
- Les 307 opérations Swagger toutes préservées à la bascule en staging
- En service en testing, staging et production en environ deux semaines
Contexte
Le BFF se situe entre une application web multi-tenant et son API interne et exposait déjà des centaines d'opérations. Son câblage HTTP, son orchestration applicative, ses contrats et son infrastructure de tests vivaient dans quelques gros fichiers partagés : le fichier principal d'endpoints avait atteint 5 657 lignes avec 128 enregistrements HTTP (133 opérations verbe-chemin sur 19 tags), un service partagé mélangeait des domaines sans rapport, et l'enregistrement des dépendances de production et de test pouvait diverger.
Avec plusieurs développeurs construisant de nouvelles zones React en même temps, tout le monde avait besoin des mêmes surfaces BFF. La structure était devenue un goulot d'étranglement de merge et de coordination plus qu'un problème de qualité de code — et l'objectif était de rendre chaque feature compréhensible et modifiable indépendamment tout en préservant routes, payloads, codes de statut, authentification, comportement tenant et compatibilité frontend.
Ce que j'ai fait
- J'ai inventorié la surface HTTP et l'ai figée dans des snapshots Swagger/OpenAPI avant de déplacer quoi que ce soit.
- J'ai conçu la tranche par feature : React / RTK Query → /api/bff → module d'endpoints de la feature → service de la feature → client API aval scoped par utilisateur → contrat de la feature → adaptateur frontend et modèle UI.
- J'ai introduit une méthode de composition partagée unique pour l'enregistrement des dépendances, utilisée par l'hôte de production et par l'hôte de test, pour que les deux ne puissent plus diverger ; validation de session, contexte tenant et accès à l'API aval sont restés partagés au lieu d'être copiés dans chaque tranche.
- J'ai déplacé des groupes d'endpoints, la logique applicative et les DTO par étapes contrôlées, en comparant après chaque pas routes, operation ID, tags et métadonnées de payload avec la baseline.
- J'ai ajouté des garde-fous au démarrage, sur les routes, les operation ID, les tags et la résolution des dépendances, plus un contrôle explicite de build et de fraîcheur après avoir découvert qu'un assembly compilé obsolète pouvait faire passer à tort la comparaison Swagger.
- J'ai rédigé du matériel d'onboarding en Markdown et HTML pour les développeurs travaillant dans la nouvelle structure, puis j'ai optimisé le quality gate local et les fixtures de test partagées quand la première version s'est révélée trop lente pour le travail quotidien sur les pull requests.
Décisions
Extraction, pas réécriture
La logique existante des features a gardé son comportement et ses auteurs. Ma responsabilité portait sur les frontières, la séquence et le filet de sécurité, pas sur la réimplémentation des règles métier.
Le contrat d'abord
Les snapshots OpenAPI définissaient ce que « inchangé » voulait dire : routes, formes de payload, codes de statut, authentification et comportement tenant devaient correspondre à la baseline à chaque étape avant d'entamer la suivante.
Une seule composition root pour la production et les tests
Le même catalogue d'enregistrements alimente les deux hôtes. Les préoccupations transversales restent partagées ; une tranche ne possède que ce qui est propre à sa feature.
Un gate strict d'abord, puis le rendre peu coûteux
Le premier quality gate protégeait contrats et tests mais demandait aux développeurs de se souvenir d'étapes supplémentaires, et certains contrôles locaux étaient assez lents pour interrompre le travail normal sur les PR. Garder la protection en réduisant le coût a été la seconde moitié du travail.
La difficulté concrète
Le refactor devait avancer pendant que les mêmes fichiers étaient modifiés par d'autres développeurs pour de nouvelles features : il a donc progressé par étapes assez petites pour être mergées et vérifiées — jamais comme une seule branche de longue durée.
La découverte du faux vert a compté : un assembly compilé obsolète faisait passer la comparaison des contrats alors que le code avait changé. Faire construire et vérifier la fraîcheur au garde-fou en premier l'a transformé d'un confort en une vraie protection.
Résultat
- À la bascule en staging, le fichier central d'endpoints est passé de 5 657 à 81 lignes et de 128 enregistrements à zéro handler ; les fichiers d'endpoints dédiés sont passés de 18 à 42, les classes de service du BFF de 20 à 40 et les fichiers de contrat par feature de 18 à 40, avec les 307 opérations Swagger toutes préservées.
- La structure a atteint testing, staging et production et est devenue le modèle utilisé par l'équipe. Je n'ai observé aucune régression causée par l'extraction, et environ deux semaines après l'identification du goulot d'étranglement la structure modulaire fonctionnait dans tous les environnements.
- De nouvelles zones pouvaient être construites en parallèle sans concentrer des travaux sans rapport dans un même fichier d'endpoint, de service, de contrat ou de test ; les chemins des requêtes sont devenus plus faciles à suivre, pour les personnes comme pour l'outillage assisté par IA.
- Sur un snapshot ultérieur, l'architecture avait grandi à 92 modules d'endpoints, 91 classes de service et 93 fichiers de contrat, toujours sans handler dans la composition root — une croissance qui appartient au travail produit de toute l'équipe, pas au seul refactor.
Les chiffres, avec leurs limites
- Fichier central d'endpointsAvant5 657 lignesAprès81 lignesce fichier uniquement ; logique redistribuée, pas supprimée
- Handlers HTTP dans la composition rootAvant128 enregistrementsAprès0
- Fichiers d'endpoints dédiésAvant18Après42
- Classes de service du BFFAvant20Après40
- Fichiers de contrat par featureAvant18Après40
- Opérations SwaggerAvant307Après307préservées à la bascule en staging
Ce que ces chiffres ne disent pas
- La réduction du nombre de lignes concerne le fichier central de composition : le code et la complexité totale du BFF ont été redistribués dans des frontières par feature, pas réduits dans la même proportion.
- Aucune métrique indépendante n'a été conservée sur la fréquence des conflits de merge, le lead time, le taux de défauts ou les heures économisées. Le déploiement dans tous les environnements et l'absence de régressions reposent sur l'observation directe pendant le travail.
- Les comptages ultérieurs (92 / 91 / 93) reflètent le développement continu de l'équipe, et l'accélération du quality gate n'a pas fait l'objet d'un benchmark.
Ce que ça prouve
Je sais prendre un goulot d'étranglement partagé dans un backend en production et le restructurer sous garantie de préservation des contrats, par étapes que l'équipe peut absorber — puis rendre les protections assez peu coûteuses pour survivre à l'usage quotidien.
Attribution
L'extraction, le modèle de composition et les garde-fous sont mon travail, avec une analyse et une documentation assistées par IA sous ma revue. Les features déplacées dans les nouvelles tranches ont été écrites par des collègues et restent les leurs.