NestJS & @cqrs-ddd: Ενσωμάτωση ή Μετάβαση
Ομάδες που χρησιμοποιούν NestJS έχουν δύο αρχιτεκτονικές επιλογές όταν υιοθετούν το @cqrs-ddd:
- Διατήρηση του NestJS και ενίσχυσή του με το
@cqrs-ddd/nestjs: Διατηρείτε τους υπάρχοντες handlers, τα modules, το dependency injection και τους controllers του@nestjs/cqrs. Ο adapter τυλίγει τους handlers σε pipelines συμπεριφορών (behaviors), παρέχει κεντρικό exception filtering και διαχειρίζεται το correlation και το job context. - Πλήρης αντικατάσταση του NestJS με το
@cqrs-ddd/cqrs: Δημιουργία εφαρμογών χωρίς framework σε απλό Node.js, Fastify ή Express, κατασκευάζοντας τους handlers μεnewκαι καταχωρίζοντάς τους στοcreateCqrs().
Σύγκριση Αρχιτεκτονικής
Ενότητα με τίτλο «Σύγκριση Αρχιτεκτονικής»| Διάσταση | Καθαρό @nestjs/cqrs |
NestJS + @cqrs-ddd/nestjs |
@cqrs-ddd/cqrs χωρίς framework |
|---|---|---|---|
| Handler Decorators | @CommandHandler, @QueryHandler, @EventsHandler |
Ίδιοι decorators του @nestjs/cqrs + @UsePipeline, @SkipPipeline |
Ίδια ονόματα από το @cqrs-ddd/cqrs + @UsePipeline, @SkipPipeline |
| Buses | CommandBus, QueryBus, EventBus |
Ίδια buses του @nestjs/cqrs |
Ίδια ονόματα buses, από το createCqrs() |
| Pipeline Behaviors | Κανένα (απαιτούνται interceptors) | Μεταγλωττίζει και εκτελεί το @cqrs-ddd/pipeline γύρω από handlers |
Μεταγλωττίζει και εκτελεί το @cqrs-ddd/pipeline γύρω από handlers |
| DI & Containers | IoC container του NestJS | IoC container του NestJS παρέχει behaviors και handlers | Χωρίς container: ρητή χρήση new στο composition root |
| Exception Handling | Χειροκίνητα exception filters | Αυτόματο ErrorFilter που μετατρέπει σφάλματα σε HttpException |
Framework-neutral: το HTTP επίπεδο αντιστοιχίζει σφάλματα (domainErrorHttpStatus) |
| Διαγνωστικοί έλεγχοι εκκίνησης | Σιωπηρή επικάλυψη σε διπλότυπους handlers | Επαληθεύει singleton scope, μοναδικότητα providers, συμβόλαια behaviors | Αποτυγχάνει σε διπλότυπους handlers, ελλιπείς decorators, παραβιάσεις συμβολαίων |
| Αποτύπωμα Runtime | NestJS runtime (~40MB+ εκκίνηση) | NestJS runtime | Ελάχιστο (μηδενικό framework overhead, άμεση εκκίνηση) |
Επιλογή 1: Ενσωμάτωση στο NestJS μέσω του @cqrs-ddd/nestjs
Ενότητα με τίτλο «Επιλογή 1: Ενσωμάτωση στο NestJS μέσω του @cqrs-ddd/nestjs»Εάν εργάζεστε ήδη σε NestJS ή επιθυμείτε το οικοσύστημα controllers και dependency injection του Nest, εγκαταστήστε το @cqrs-ddd/nestjs:
pnpm add @cqrs-ddd/nestjs @cqrs-ddd/pipeline @cqrs-ddd/core @nestjs/cqrsΤι διατηρείτε
Ενότητα με τίτλο «Τι διατηρείτε»- Όλους τους ορισμούς
@Module(),@Injectable(),@Controller(). - Τα επίσημα imports του
@nestjs/cqrs:CqrsModule,CommandBus,QueryBus,EventBus,@CommandHandler,@QueryHandler,@EventsHandler. - Οι handlers υλοποιούν τα
ICommandHandler,IQueryHandler,IEventHandler. - Οι controllers κάνουν inject τα
CommandBusκαιQueryBusόπως συνήθως.
Τι προσθέτετε
Ενότητα με τίτλο «Τι προσθέτετε»-
PipelineModule.forRoot()στοAppModule:@Module({imports: [CqrsModule.forRoot(),PipelineModule.forRoot({sources: { tenantId: getTenantId, correlationId: getCorrelationId },globalBehaviors: [{ scope: 'all', before: [logging()] }],}),],providers: [{ provide: APP_FILTER, useClass: ErrorFilter },{ provide: LoggingBehavior, useFactory: () => new LoggingBehavior(new Logger('Pipeline')) },],})export class AppModule {} -
Δηλώσεις pipeline σε handlers:
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';import { UsePipeline, SkipPipeline } from '@cqrs-ddd/pipeline';import { idempotent } from '@cqrs-ddd/pipeline-idempotency';import { validated } from '@cqrs-ddd/pipeline-zod';@CommandHandler(CreateInvoiceCommand)@UsePipeline(validated(CreateInvoiceSchema), idempotent({ keyFactory }))export class CreateInvoiceHandler implements ICommandHandler<CreateInvoiceCommand> {async execute(command: CreateInvoiceCommand) {// Εκτελείται μέσα στο pipeline}} -
Δημοσίευση Domain Events: Κληρονομήστε από τον
CommandBaseHandlerτου@cqrs-ddd/core/applicationκαι περάστε το injectedEventBusτου@nestjs/cqrs. Όταν ηhandle(command)επιστρέφει έναAggregateRoot, τα μη καταχωρισμένα domain events δημοσιεύονται αυτόματα μέσω τουeventBus.publishAll()και καθαρίζονται.
Επιλογή 2: Μετάβαση από το NestJS στο @cqrs-ddd/cqrs
Ενότητα με τίτλο «Επιλογή 2: Μετάβαση από το NestJS στο @cqrs-ddd/cqrs»Εάν ο στόχος σας είναι η κατάργηση του NestJS για μια ελαφριά αρχιτεκτονική χωρίς framework (Express, Fastify, AWS Lambda, Cloudflare Workers), χρησιμοποιήστε το @cqrs-ddd/cqrs:
pnpm add @cqrs-ddd/cqrs @cqrs-ddd/pipeline @cqrs-ddd/coreΤι μεταφέρεται αυτούσιο
Ενότητα με τίτλο «Τι μεταφέρεται αυτούσιο»Το API του πυρήνα CQRS ταιριάζει με το @nestjs/cqrs:
@nestjs/cqrs |
@cqrs-ddd/cqrs |
|---|---|
@CommandHandler(Command) |
@CommandHandler(Command) από το @cqrs-ddd/cqrs |
@QueryHandler(Query) |
@QueryHandler(Query) από το @cqrs-ddd/cqrs |
@EventsHandler(...Events) |
@EventsHandler(...Events) από το @cqrs-ddd/cqrs |
ICommandHandler<TCommand> |
ICommandHandler<TCommand> από το @cqrs-ddd/cqrs |
IQueryHandler<TQuery> |
IQueryHandler<TQuery> από το @cqrs-ddd/cqrs |
IEventHandler<TEvent> |
IEventHandler<TEvent> από το @cqrs-ddd/cqrs |
CommandBus, QueryBus, EventBus |
Επιστρέφονται από το createCqrs() |
Τι αναλαμβάνει η ίδια η εφαρμογή
Ενότητα με τίτλο «Τι αναλαμβάνει η ίδια η εφαρμογή»| Ευθύνη NestJS | Ισοδύναμο @cqrs-ddd |
|---|---|
@Module(), @Injectable(), providers |
Μια απλή συνάρτηση composition root που δημιουργεί εξαρτήσεις με new |
NestFactory.create(AppModule) |
const cqrs = createCqrs(options); cqrs.register(...handlers); |
| Lifecycle hooks τερματισμού | await cqrs.close(), και στη συνέχεια κλείσιμο των pools βάσης και συνδέσεων redis |
| Controllers, guards, interceptors, pipes | Εγγενείς διαδρομές και middleware του HTTP framework· δείτε HTTP με Express και Fastify |
| Request-scoped providers | AsyncLocalStorage μέσω των context sources (tenantId, correlationId) |
Παράδειγμα σε Απλό Node.js
Ενότητα με τίτλο «Παράδειγμα σε Απλό Node.js»import { CommandHandler, createCqrs, type ICommandHandler,} from '@cqrs-ddd/cqrs';import { UsePipeline, LoggingBehavior } from '@cqrs-ddd/pipeline';import { audit } from '@cqrs-ddd/pipeline-audit';
class RegisterUserCommand { constructor(readonly email: string) {}}
@CommandHandler(RegisterUserCommand)@UsePipeline(audit({ action: 'user.registered' }))class RegisterUserHandler implements ICommandHandler<RegisterUserCommand> { constructor(private readonly users: UsersRepository) {}
async execute(command: RegisterUserCommand): Promise<string> { const user = await this.users.create(command.email); return user.id; }}
// Composition Rootexport function buildApplication(users: UsersRepository, auditSink: IAuditSink) { const cqrs = createCqrs({ behaviors: [new AuditBehavior(auditSink)], globalBehaviors: { before: [LoggingBehavior] }, });
cqrs.register(new RegisterUserHandler(users));
return cqrs;}Διαφορές στη Συμπεριφορά Runtime
Ενότητα με τίτλο «Διαφορές στη Συμπεριφορά Runtime»- Αυστηρή καταχώριση handlers: Η καταχώριση δεύτερου handler για την ίδια εντολή ή ερώτημα αποτυγχάνει αμέσως στο
cqrs.register(). Το NestJS σιωπηρά αντικαθιστά τους προηγούμενους με τον τελευταίο καταχωρισμένο. - Ασύγχρονα σφάλματα απόντος handler: Στο
@cqrs-ddd/cqrs, ταcommandBus.execute()καιqueryBus.execute()απορρίπτουν ασύγχρονα μεCommandHandlerNotFoundExceptionήQueryHandlerNotFoundException. Το@nestjs/cqrsεκτοξεύει σύγχρονα. - Ομαλός τερματισμός (Graceful Shutdown): Το
cqrs.close()περιμένει την ολοκλήρωση όλων των ασύγχρονων event handlers πριν επιλυθεί. Στο NestJS, τοapp.close()δεν παρακολουθεί την ολοκλήρωση των παρασκηνιακών event handlers. - Απομόνωση σφαλμάτων events: Οι αποτυχίες των event handlers αποστέλλονται στο
UnhandledExceptionBusκαι καταγράφονται. Σε αντίθεση με το@nestjs/cqrs, οι handlers συνεχίζουν να λαμβάνουν μεταγενέστερα events ακόμα και μετά από προηγούμενη αποτυχία. - Δεν παρέχονται στο
@cqrs-ddd/cqrs: Sagas, RxJS pipes μεofType,AsyncContextκαι μηχανισμοί συγχώνευσης τουEventPublisher. Τα aggregates δημοσιεύουν events μέσω τουCommandBaseHandler.
Επιλέγοντας τη Σωστή Προσέγγιση
Ενότητα με τίτλο «Επιλέγοντας τη Σωστή Προσέγγιση»- Επιλέξτε την Επιλογή 1 (
@cqrs-ddd/nestjs) όταν εργάζεστε σε υπάρχουσα βάση κώδικα NestJS, χρησιμοποιείτε NestJS microservices ή GraphQL, ή βασίζεστε έντονα στο οικοσύστημα controllers και DI modules του Nest. - Επιλέξτε την Επιλογή 2 (
@cqrs-ddd/cqrs) όταν χτίζετε νέα microservices, serverless functions, APIs υψηλής απόδοσης σε Fastify, ή εφαρμογές όπου απαιτείται μηδενική επιβάρυνση dependency injection και άμεση εκκίνηση.