Pourquoi j'ai choisi le Domain-Driven Design pour ArtFolio
Le contexte
Quand j'ai commencé ArtFolio comme projet de fin de formation, j'avais le choix entre une architecture simple (controllers + services + repositories dans un seul module) et quelque chose de plus structuré. J'ai choisi le Domain-Driven Design. Pas pour la complexité, mais parce que le projet avait des règles métier réelles : gestion d'utilisateurs avec des rôles différents (artiste, amateur, modérateur), upload de fichiers, demandes GDPR, et un système de permissions granulaires.
Ce que DDD a changé concrètement
Le domaine ne dépend de rien
La décision la plus importante a été de garder la couche domain complètement indépendante du framework. Mes entités, value objects et interfaces de repository sont du TypeScript pur. Aucun décorateur NestJS, aucune référence à TypeORM. Quand je teste une règle métier, je n'ai besoin ni d'une base de données ni du conteneur d'injection de NestJS.
En pratique, ça veut dire que UserId est un value object qui valide le format UUID à la construction. Si quelqu'un essaie de passer un PostId là où un UserId est attendu, le compilateur TypeScript le refuse. C'est de la sécurité à deux niveaux : compilation et exécution.
Les use cases comme unité de travail
Chaque opération métier est un use case isolé : CreateArtistUseCase, GetAllPostsUseCase, HandlePersonalDataRequestUseCase. Chaque use case ne fait qu'une seule chose. Il reçoit ses dépendances par injection (via des interfaces, jamais des implémentations concrètes) et orchestre la logique métier.
L'avantage : quand un test échoue, je sais exactement où regarder. Quand un nouveau développeur rejoint le projet, il peut lire un use case et comprendre un flux métier complet sans naviguer entre 10 fichiers.
L'infrastructure est remplaçable
Les repositories implémentent des interfaces définies dans le domaine. Si je voulais remplacer PostgreSQL par MongoDB, je n'aurais qu'à écrire de nouvelles implémentations de repository. Le domaine et la couche application ne changeraient pas.
Ce n'est pas un scénario théorique. Pendant le développement, j'ai changé la stratégie de cache (d'un simple objet en mémoire à Redis) sans toucher à un seul use case. L'interface de cache était la même, seule l'implémentation a changé.
Les pièges que j'ai rencontrés
Trop de couches pour des opérations simples
Pour un simple GET qui retourne une liste, le chemin Controller → Use Case → Repository → Base de données peut sembler excessif. J'ai appris à accepter que certains use cases sont triviaux, et c'est normal. La régularité de la structure compte plus que l'optimisation de chaque fichier.
Le mapping entre couches
Transformer un DTO en entité domain, puis en entité TypeORM, puis en réponse DTO, c'est du code répétitif. J'ai utilisé class-transformer et des DTOs bien typés pour réduire le boilerplate, mais il reste présent. C'est le coût de la séparation des couches.
Les transactions cross-repository
Créer un artiste implique de sauvegarder un utilisateur, un asset (photo de profil), un post initial et des catégories. Tout ça dans une seule transaction. DDD pur dirait d'utiliser des domain events, mais dans un backend NestJS de cette taille, j'ai opté pour une transaction DataSource.transaction() avec un EntityManager scopé. Si quelque chose échoue, tout est rollback, et les fichiers uploadés sont supprimés. Pragmatique plutôt que dogmatique.
Ce que je referais
Oui, je referais le même choix. Le DDD n'est pas une architecture pour impressionner. C'est une façon de structurer le code pour que les décisions métier soient visibles, testables et isolées de l'infrastructure. Pour un projet avec de vrais invariants métier (rôles, permissions, GDPR), c'est le bon outil.
Pour un CRUD simple sans logique métier ? Un module NestJS classique suffit. L'architecture doit servir le problème, pas l'inverse.