← Retour au blog
architectureangularscalabilityfeature-basedlayeredmodule-design

Architecture feature-based vs layered : au-delà du choix binaire

L'architecture logicielle est rarement un choix définitif. Pourtant, les développeurs se polarisent souvent entre deux camps : d'un côté le feature-based (organisation par domaine métier), de l'autre le layered (découpe par couche technique). Cette opposition masque une réalité bien plus nuancée. Un projet qui commence en layered pur devient rapidement un chaos de dépendances circulaires. Un projet feature-based sans structure transversale deviendra un empilement de silos incompréhensibles. La vraie question n'est pas de choisir l'un ou l'autre, mais de comprendre quand chacun apporte de la valeur et comment les fusionner intelligemment pour que votre code reste maintenable à mesure que l'équipe grandit.

Architecture layered : la structure qui rassure et qui piège

L'architecture par couches (controllers, services, repositories, modèles) est ce qu'on enseigne en école. Elle séduit les CTO junior et les leads qui viennent du monde Java entreprise : elle impose une discipline, crée une hiérarchie claire des responsabilités, et facilite l'onboarding des nouveaux devs. Un service HTTP appelle un service métier, qui appelle un repository, qui parle à la base de données. C'est linéaire, c'est prévisible. Mais à la première croissance, cette approche montre ses limites. Imaginez un projet avec dix features : vous vous retrouvez avec un dossier services/ contenant 40 fichiers, un dossier repositories/ tout aussi massif, et personne ne sait vraiment quel service dépend de quel autre. Les imports deviennent un cauchemar : un service de facturation importe un service de paiement, qui lui-même importe un service de notification, qui remonte à la facturation pour logger. Les dépendances circulaires se multiplient. Le layered pure scale mal parce qu'il confond organisation physique et séparation des responsabilités. La vraie douleur arrive quand il faut modifier une feature : vous naviguez entre quatre couches, parfois dix fichiers, sans jamais voir le contexte métier complet. Un changement dans la gestion des stocks peut toucher le controller de commande, le service de commande, le service de stock, le repository de stock, et le modèle de stock. Difficult à tester, difficile à refactoriser, difficile à déployer de manière indépendante.

Feature-based : l'organisation métier qui devient du spaghetti

Le feature-based répond à cette frustration en organisant le code autour des domaines métier. Un dossier features/checkout/ , un autre features/inventory/ , chacun contenant tous ses composants, services et logique. C'est plus proche de la réalité : l'équipe qui travaille sur le checkout vit dans un univers cohérent, elle n'a pas à naviguer dans quinze couches abstraites. Les imports restent locaux, la dépendance est explicite et directe. C'est aussi plus facile à déployer indépendamment : une feature peut avoir sa propre version, son propre cycle de déploiement. Les équipes cross-fonctionnelles adorent ça. Mais sans garde-fou, le feature-based devient une jungle. Chaque feature se définit ses propres conventions : l'une met la logique dans les composants, l'autre dans les services, une troisième mélange les deux. Un utility partagé entre trois features vit où ? Dans la première feature qui l'a créé ? Dupliqué trois fois ? Personne ne sait. À mesure que les features grossissent, certaines absorbent d'autres en tant que sous-dossiers, créant une hiérarchie implicite et incompréhensible. Et la réutilisation transversale (authentification, logging, gestion des erreurs) devient un casse-tête : vous finissez avec un dossier shared/ qui concentre tout ce qui n'a pas trouvé sa place, tuant l'idée même du découpage par domaine.

La fusion intelligente : hybrid layering

La solution n'est pas d'en choisir un, mais de les hybrider. Organisez le top-level par feature (métier), mais à l'intérieur de chaque feature, respectez une structure en couches légère. Voici comment ça pourrait ressembler :

src/
  features/
    checkout/
      components/
        checkout-form.component.ts
      services/
        checkout.service.ts
        checkout-calculator.service.ts
      models/
        checkout.model.ts
      checkout.module.ts
    inventory/
      components/
        stock-display.component.ts
      services/
        inventory.service.ts
      models/
        inventory.model.ts
      inventory.module.ts
  shared/
    core/
      guards/
      interceptors/
      error-handling/
    ui/
      buttons/
      cards/

Cette structure clarifie deux niveaux : au premier niveau, c'est métier (checkout, inventory, user). Au second niveau, à l'intérieur de chaque feature, c'est technique (components, services, models). Les dépendances restent prévisibles : une feature ne communique avec une autre que via des services injectables, jamais par import direct de composants. Les utilitaires transversaux (guards, interceptors, composants réutilisables) vivent dans shared/ , mais avec une règle stricte : shared/ dépend de rien d'autre, les autres dépendent de shared/ . Cette asymétrie élimine les circulaires.

Adapter à la croissance réelle

Le vrai art est de savoir à quel moment basculer d'une structure à l'autre. Un projet de trois mois avec deux devs ? Layered simple suffit, c'est rapide. Un projet avec trois équipes et dix features ? Hybrid feature-based devient obligatoire. Mais attention : ne pas anticiper trop. J'ai vu des équipes de trois personnes mettre en place une architecture si sophistiquée qu'elles passaient plus de temps à respecter la structure qu'à coder. Commencez simple, refactorisez quand la douleur devient réelle. Avec Angular, utilisez les modules (ou les standalone components avec des fichiers de barrel exports) pour matérialiser les frontières. Chaque feature est un module qui exporte explicitement ce qui peut être consommé. Cela force la discipline : si tu dois importer quelque chose d'une autre feature, tu le fais via son API publique (le module ou le barrel export), pas par un import direct d'un fichier privé.

Le piège courant : la feature qui devient une couche

Le danger le plus fréquent est de confondre feature avec couche technique. Un dossier features/services/ n'a aucun sens : services n'est pas un domaine métier, c'est un concept technique. De même, un dossier features/common/ qui contient tout ce qui est réutilisable. Si tu hésites, demande-toi : est-ce que mes utilisateurs ou mon équipe métier reconnaissent ce concept ? Si la réponse est non, ce n'est pas une feature, c'est un utilitaire qui doit vivre dans shared/ . L'autre piège est de créer des mega-features qui durent trop longtemps. Une feature doit rester petite et cohésive : checkout, inventory, user-profile, notifications. Pas « ecommerce » ou « business ». Plus une feature est grande, plus elle aura besoin d'une structure interne sophistiquée, et tu reviendras à des problèmes de layered pur.

Conclusion opérationnelle

L'architecture feature-based vs layered n'est pas une bataille idéologique, c'est un choix d'outils. Commencez par comprendre votre équipe, votre domaine métier, et la taille prévisible du projet. Puis utilisez le hybrid approach : feature-based en haut pour la cohérence métier, layering léger en bas pour la structure technique. Enforcer l'asymétrie des dépendances (shared ne dépend de rien, tout le reste peut en dépendre). Utilisez les module boundaries (Angular modules ou barrel exports) comme des contrats explicites. Refactorisez régulièrement, pas une fois tous les deux ans. Et surtout, écoutez votre code : s'il devient difficile de naviguer, d'importer, ou de tester, c'est que votre architecture ne match plus votre réalité. À ce moment-là, il n'y a pas honte à pivoter. Les meilleures architectures ne sont pas celles qui ont été planifiées sur un tableau blanc au jour un, mais celles qui ont évoluées avec le produit et l'équipe.

Développeur Angular & Mobile freelance — Strasbourg.

© 2026 Emilien Pons — Tous droits réservés.Conçu avec Angular, PrimeNG et ❤️