🏗️ Comment j’organise mes solutions .NET pour des projets scalables et faciles à maintenir
J’ai eu l’occasion de travailler sur des projets très différents : applications web complexes, APIs métiers, plateformes 3D, outils internes, projets mobiles…
Une chose revient toujours : la structure d’un projet conditionne sa qualité et sa durée de vie.
Une mauvaise structure = un projet fragile.
Une bonne structure = un projet qui respire, évolue et reste propre.
Dans cet article, je partage comment j’organise mes solutions .NET, de manière simple et pragmatique, pour obtenir un code :
✔ propre
✔ testable
✔ évolutif
✔ scalable
✔ agréable à maintenir
🔹 1. Séparer clairement le domaine, l’application, l’infrastructure et le web
J’utilise souvent une organisation inspirée de Clean Architecture, mais sans aller dans l’excès.
Le but est simple : chaque couche doit avoir une responsabilité.
Voici la structure que j’utilise le plus souvent :
src/
├─ Project.Domain
├─ Project.Application
├─ Project.Infrastructure
└─ Project.Api
Project.Domain
➡️ Le cœur métier
➡️ Entités, value objects, règles métier
➡️ Pas de dépendance technique
Project.Application
➡️ Les cas d’usage
➡️ Services applicatifs
➡️ Interfaces (Ports) vers l’infra
➡️ DTOs, validations, handlers
➡️ Utilisation légère de MediatR selon les besoins
Project.Infrastructure
➡️ Implémentation des interfaces
➡️ EF Core, SQL Server, Redis, Files, API externes
➡️ Repositories, configuration, migrations
Project.Api
➡️ Exposition HTTP
➡️ JWT / Policies / Rate Limiting
➡️ Contrôleurs, minimal APIs, endpoints
➡️ Mapping entre Application et API
Chacune a son rôle, aucune ne dépasse.
C’est la base d’une architecture saine.
🔹 2. Tout isoler derrière des Interfaces — surtout ce qui touche à l’extérieur
Base de la maintenabilité :
👉 Je ne travaille jamais directement avec EF, Redis, ou un service externe dans la couche Application.
Je crée systématiquement des interfaces :
public interface IEmailSender
{
Task SendAsync(string to, string subject, string body);
}
Puis l’implémentation se trouve en Infrastructure :
public class SmtpEmailSender : IEmailSender { ... }
Avantages :
- testabilité (mock facile)
- découplage total
- remplacement rapide d’une technologie
🔹 3. Toujours centraliser la configuration
Je n’éparpille jamais le code de config.
Tout est regroupé dans un dossier Configuration ou Options :
Project.Api/
└─ Config/
├─ JwtOptions.cs
├─ CorsOptions.cs
├─ DatabaseOptions.cs
└─ RateLimitOptions.cs
Ensuite, j’utilise IOptions<T> proprement.
✔️ C’est plus clair
✔️ Ça évite la magie
✔️ Ça rend la maintenance très simple
🔹 4. Une solution doit rester lisible : pas plus de 15–20 projets
Un piège fréquent (surtout dans les grosses équipes) :
💥 multiplier les projets sans raison
Je garde toujours une convention simple :
➡️ Tant que le projet fait moins de 500 fichiers → 4 projets (Domain, App, Infra, API) suffisent.
On n’ajoute des projets supplémentaires que si nécessaire :
- un module séparé ?
- un adapter externe ?
- un background worker ?
Mais jamais juste “pour faire Clean Architecture comme dans un tuto”.
🔹 5. Un dossier “Shared” propre pour les cross-cutting concerns
Les éléments transverses (log, date providers, exceptions, result pattern…) vont dans un dossier partagé :
Project.Domain/
└─ Shared/
├─ Result.cs
├─ DomainException.cs
└─ TimeProvider.cs
Je fais très attention à ne pas en abuser.
Un “Shared” trop gros = dette technique.
🔹 6. Prévoir la testabilité dès le début
Je crée toujours le projet de tests au même moment que la solution :
tests/
└─ Project.Tests
Puis je m’assure que tout est testable par construction :
- pas de logique dans les contrôleurs
- dépendances injectées
- classes petites
- méthodes pures quand c’est possible
- validation dans l’Application, pas dans l’API
- mocks faciles via interfaces
Ce principe m’a beaucoup servi chez AXA, où les tests faisaient partie du quotidien.
🔹 7. Utiliser des conventions nommage strictes
La structure doit être lisible en 2 secondes :
UserController
UserService
UserRepository
UserCreatedEvent
GetUserQuery
UpdateUserCommand
Pourquoi ?
➡️ Parce que le cerveau humain comprend mieux les conventions que les surprises.
➡️ Parce que l’onboard des nouveaux devs devient instantané.
🔹 8. Documenter les choix (sans écrire un roman)
Je documente uniquement ce qui compte :
- diagramme simple (mermaid, draw.io)
- règles importantes
- frontières du domaine
- modules principaux
- flux essentiels
Tout tient souvent dans un README clair à la racine du repo.
🎯 Conclusion : une architecture simple, propre, et adaptée au métier
Il n’existe pas d’architecture parfaite.
Mais il existe une approche réaliste :
✔ garder les responsabilités claires
✔ éviter la complexité inutile
✔ découpler intelligemment
✔ préparer les tests
✔ structurer pour les évolutions futures
C’est cette approche que j’essaie d’appliquer dans tous mes projets — et c’est ce qui fait la différence entre une solution qui survit… et une solution qui prospère.