ADR-0001 : Clean Architecture pour les services back-end
| Statut | Accepté |
| Date | 2025-10 |
| Décideurs | Équipe de développement Edukit |
| Périmètre | Edukit-OrgService, Edukit-VM |
Contexte
Edukit comporte deux services back-end (.NET 9) au domaine métier non trivial : gestion des organisations, des utilisateurs, des groupes, des séances et des licences pour OrgService ; provisionnement Proxmox piloté par saga pour VmService. Ces domaines portent des invariants forts (machines à états des VM et des séances, règles d’autorisation par rôle, cycle de vie des licences) qui doivent rester valides quelle que soit la technologie d’accès aux données ou de transport utilisée.
Plusieurs forces s’exercent simultanément :
- le métier doit pouvoir être testé sans base de données, sans Keycloak et sans Proxmox réels ;
- les dépendances d’infrastructure (EF Core, Npgsql, client Proxmox HTTP, SFTP, client Keycloak Admin, Marten, Wolverine) évoluent à leur propre rythme et ne doivent pas contaminer les règles métier ;
- deux services partagent les mêmes conventions ; un développeur productif sur l’un doit l’être immédiatement sur l’autre.
Le risque d’une architecture en couches classique mais laxiste (« le contrôleur appelle directement le DbContext ») est connu : couplage du métier à l’ORM, tests lents et fragiles, règles métier dispersées dans la couche de présentation.
Décision
Les deux services appliquent une Clean Architecture stricte, avec les dépendances orientées vers l’intérieur :
Domain ← Application ← Infrastructure ← API / Worker
- Domain : entités, objets-valeur, invariants et machines à états. Aucune dépendance externe. Convention partagée entre les deux dépôts : constructeur privé pour EF Core, constructeur public qui valide ses arguments (
ArgumentException), modificateurs privés, méthodes métier explicites qui appellentSetUpdated(). - Application : commandes et requêtes MediatR, validateurs FluentValidation, ports (interfaces) vers l’extérieur. Dépend uniquement de Domain.
- Infrastructure : implémentation des ports (dépôts EF Core, Keycloak Admin, Proxmox HTTP, SFTP, gRPC, messagerie). Dépend de Application et Domain.
- API / Worker : composition (injection de dépendances), points d’entrée HTTP, hubs SignalR, hôtes de services en arrière-plan. C’est la seule couche qui connaît toutes les autres.
VmService introduit une variante validée par l’ADR-0003 : deux points d’entrée (API et processus de travail Worker) qui partagent les mêmes couches Domain, Application et Infrastructure.
Conséquences
Bénéfices
- Le domaine est testable en isolation. Plus de 900 tests automatisés (dont la majeure partie côté Application et Domain) s’exécutent sans dépendance externe.
- Les choix d’infrastructure restent remplaçables. Le passage d’EF Core à un autre fournisseur, ou de Marten à un autre magasin, n’impacte pas les règles métier.
- L’homogénéité entre les deux services réduit la charge cognitive : mêmes dossiers, mêmes conventions, mêmes comportements de pipeline (voir ADR-0002).
- Les préoccupations transverses (autorisation, validation, journalisation) se branchent dans la couche Application sans toucher au domaine.
Coûts et limites
- Davantage de fichiers et de cérémonie (ports, adaptateurs, objets de transfert) qu’une approche directe. Le surcoût est assumé au regard de la durée de vie et de la complexité du domaine.
- La discipline doit être tenue : une couche qui « court-circuite » vers l’infrastructure casse le bénéfice. Elle est en partie protégée par l’analyse statique et la revue de demande de fusion.
Alternatives écartées
- Architecture en couches « anémique » (contrôleur → service → DbContext) : plus rapide à démarrer, mais couple le métier à l’ORM et disperse les invariants. Écartée pour un domaine à invariants forts et à longue durée de vie.
- Architecture hexagonale stricte (ports et adaptateurs « purs ») : très proche dans l’esprit ; la Clean Architecture a été retenue pour son vocabulaire de couches explicite, plus simple à transmettre à une équipe pluridisciplinaire et à un évaluateur.
- Vertical Slice Architecture pure : séduisante pour la cohésion par fonctionnalité, mais le besoin d’un domaine partagé fort entre fonctionnalités (machines à états, invariants) a fait préférer une séparation par couches, le découpage par fonctionnalité étant tout de même appliqué à l’intérieur de la couche Application (un dossier par commande / requête).