← Retour au blog

Pourquoi j'ai choisi le Domain-Driven Design pour ArtFolio

DDDNestJSArchitecture

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 est au centre et ne dépend de rien. Chaque couche extérieure dépend de la couche intérieure, jamais l'inverse.

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 même chemin pour chaque requête, même quand l'opération est triviale.

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.

Une seule transaction regroupe les quatre écritures. En cas d'échec, tout est annulé et les fichiers uploadés sont nettoyés.

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.