DDD & CQRS avec NestJS
Concepts
- DDD: Domain Driven Design ou Conception pilotée par le domaine. Technique consistant à concevoir le développement en séparant le domaine métier des considérations techniques propres à l'implémentation.
- CQRS: Command Query Responsability Segregation. Patron de conception qui sépare la partie lecture et la partie écriture afin de limiter le couplage des fonctionnalités et les effets de bords.
- NestJS: Framework orienté back-end écrit en TypeScript, inspiré de l'architecture d'Angular.
Motivations
La séparation du domaine permet de rassembler les règles métiers au même endroit et d'implémenter des tests automatisés centrés sur ces règles sans considération des contraintes purement techniques (ex: la base de données).
La séparation lecture/écriture permet de limiter les responsabilités des chaque handler
. Le patern évite, par exemple, toute tentation d'écrire de la donnée transformée lors de la lecture.
Opinion personnelle: je trouve le mécanisme Query/Command -> Bus -> Handler -> Event -> EventHandler
plutôt élégant dans la séparation des responsabilités.
Dossiers & fichiers
Pour cette application du DDD x CQRS avec NestJS, on utilise :
- Le
Service
etController
proposé par la doc officielle NestJS. LeController
dépend duService
. LeService
expose les fonctions qui lanceront lesQuery
etCommand
. - le dossier
domain
défini notre domaine.- Le modèle
User
étend la classeAggregateRoot
du modulenestjs/cqrs
. - L'interface
UserRepository
- La classe
UserFactory
qui permet de créer un nouvelUser
- Le modèle
- le dossier
application
défini lesQuery
&Command
, leurshandlers
- L'interface
UserQuery
défini les fonctions d'extraction
- L'interface
- le dossier
infrastructure
contient les implémentations deUserRepository
etUserQuery
en faisant le lien verstypeORM
- l'injection des implémentation se fait dans le
UserModule
via l'injection deProvider[]
de Nest JS
Exemple: User Module
Schéma
Déclaration
// Déclaration des implémentations qui seront injectée par Nest dans les classes application
const infrastructure: Provider[] = [
{
provide: UserInjectionToken.USER_REPOSITORY,
useClass: UserRepositoryImplement,
},
{
provide: UserInjectionToken.USER_QUERY,
useClass: UserQueryImplement,
},
]
const domain = [UserFactory]
const application = [
FindUserByIdHandler,
FindUserByEmailHandler,
CreateUserHandler,
]
@Module({
imports: [CqrsModule, PasswordModule],
providers: [...infrastructure, ...domain, ...application, UserService],
exports: [UserService],
controllers: [UserController],
})
export class UserModule {}