Après des années à refactoriser des backends .NET, un schéma d'architecture résiste toujours à la pression : la véritable séparation en couches. Voici exactement comment je la structure, et pourquoi chaque limite de couche est une décision délibérée.

Lorem ipsum dolor sit amet, consectetur adipiscing elit lobortis arcu enim urna adipiscing praesent velit viverra sit semper lorem eu cursus vel hendrerit elementum morbi curabitur etiam nibh justo, lorem aliquet donec sed sit mi dignissim at ante massa mattis.
Vitae congue eu consequat ac felis placerat vestibulum lectus mauris ultrices cursus sit amet dictum sit amet justo donec enim diam porttitor lacus luctus accumsan tortor posuere praesent tristique magna sit amet purus gravida quis blandit turpis.

At risus viverra adipiscing at in tellus integer feugiat nisl pretium fusce id velit ut tortor sagittis orci a scelerisque purus semper eget at lectus urna duis convallis. Porta nibh venenatis cras sed felis eget neque laoreet suspendisse interdum consectetur libero id faucibus nisl donec pretium vulputate sapien nec sagittis aliquam nunc lobortis mattis aliquam faucibus purus in.
Nisi quis eleifend quam adipiscing vitae aliquet bibendum enim facilisis gravida neque. Velit euismod in pellentesque massa placerat volutpat lacus laoreet non curabitur gravida odio aenean sed adipiscing diam donec adipiscing tristique risus. amet est placerat in egestas erat imperdiet sed euismod nisi.
“Nisi quis eleifend quam adipiscing vitae aliquet bibendum enim facilisis gravida neque velit euismod in pellentesque massa placerat”
Eget lorem dolor sed viverra ipsum nunc aliquet bibendum felis donec et odio pellentesque diam volutpat commodo sed egestas aliquam sem fringilla ut morbi tincidunt augue interdum velit euismod eu tincidunt tortor aliquam nulla facilisi aenean sed adipiscing diam donec adipiscing ut lectus arcu bibendum at varius vel pharetra nibh venenatis cras sed felis eget.
Chaque projet commence simplement. Un contrôleur appelle un service. Un service appelle la base de données. Ça fonctionne, jusqu'au jour où ça ne fonctionne plus. Six mois plus tard, vous avez des contrôleurs qui contiennent de la logique métier, des services qui accèdent directement à la base de données, et une suite de tests qui nécessite une connexion SQL Server active pour exécuter une seule assertion.
J'ai refactorisé ce schéma plus de fois que je ne saurais compter. La solution est toujours la même : une véritable séparation en couches, imposée par la structure du projet et pas seulement par des conventions de nommage.
Ma structure standard correspond directement au modèle d'architecture en oignon (Onion Architecture), adapté aux réalités du développement .NET en entreprise.
Aucune dépendance. Aucune. Cette couche contient vos entités, vos objets de valeur, vos événements de domaine et vos interfaces de dépôt. Elle définit ce que le système modélise — des contrats sans implémentation. Si ce projet référence un ORM, un logger ou un client HTTP, quelque chose ne va pas.
Les cas d'usage, les commandes, les requêtes (façon CQRS avec MediatR), les validateurs et les DTO vivent ici. La couche Application ne dépend que de Domain. Elle orchestre le travail — elle appelle des méthodes du domaine, déclenche des événements — mais elle ne parle jamais directement à une base de données. Elle fait confiance aux interfaces définies dans Domain.
Les dépôts EF Core, les clients Azure Blob, les services SMTP, les wrappers d'API externes — tout est ici. Infrastructure implémente les interfaces déclarées dans Domain. C'est la seule couche qui devrait jamais importer un package NuGet pour des entrées/sorties. Remplacer un dépôt SQL Server par Cosmos DB ne devrait nécessiter des changements que dans un seul projet.
Contrôleurs, middlewares, validateurs de requêtes, mappeurs de réponses. La couche Presentation traduit le HTTP en commandes Application. Elle ne connaît rien de votre base de données ni de vos règles métier — elle mappe et transmet.
Chez ADENES, nous avons hérité d'un système où la logique de traitement des sinistres d'assurance était directement intégrée dans les contrôleurs de l'API. Extraire cela vers une structure Domain + Application correcte a pris trois mois — mais a réduit le temps nécessaire pour tester une nouvelle règle de sinistre, passant d'heures à quelques minutes. Les tests unitaires s'exécutent sans base de données. Les nouveaux développeurs sont opérationnels en quelques jours, pas en semaines.
« L'objectif n'est pas une architecture propre pour le principe. L'objectif est une base de code où le coût du changement reste faible à mesure que le système grandit. »
Les dépendances pointent toujours vers l'intérieur. Domain ne connaît rien. Application connaît Domain. Infrastructure connaît Domain et Application. Presentation connaît Application. Imposez cela via les références de projet .NET — pas via des conventions de nommage. Si une référence crée une dépendance circulaire ou pointe dans le mauvais sens, la compilation échoue. C'est votre architecture qui s'applique elle-même.
N'essayez pas de tout refactoriser d'un coup. Commencez par un seul concept de domaine. Extrayez l'interface. Déplacez l'implémentation. Écrivez un test unitaire qui s'exécute sans base de données. Ce premier test est une preuve de concept — et c'est généralement suffisant pour convaincre le reste de l'équipe.
Ce sont les mêmes principes de séparation en couches qui régissent le backend de Contractly Pro — devis, contrats, facturation et paiements vivent chacun dans leur propre couche testable plutôt que dans un service monolithique enchevêtré. Essayer Contractly Pro gratuitement →