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 RGPD, 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.
Le cache suit le même principe. Il passe par l’abstraction cache-manager de NestJS : le store est en mémoire aujourd’hui, mais le remplacer par Redis ne demanderait qu’un changement de configuration, sans toucher à un seul use case. La logique métier ignore où sont stockées les données.
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, RGPD), 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.