From 9c66d20d7c000af5eddacbe2519a84e1ab2f540b Mon Sep 17 00:00:00 2001 From: ZimGil Date: Thu, 27 Jul 2023 19:04:37 +0300 Subject: [PATCH 1/2] feat(logs): log duplicate query handlers --- src/decorators/query-handler.decorator.ts | 5 ++-- .../queries/query-metadata.interface.ts | 1 + src/query-bus.ts | 26 ++++++++++++------- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/decorators/query-handler.decorator.ts b/src/decorators/query-handler.decorator.ts index e52e3a83..589d3f0c 100644 --- a/src/decorators/query-handler.decorator.ts +++ b/src/decorators/query-handler.decorator.ts @@ -2,6 +2,7 @@ import 'reflect-metadata'; import { IQuery } from '../interfaces'; import { QUERY_HANDLER_METADATA, QUERY_METADATA } from './constants'; import { v4 } from 'uuid'; +import { Type } from "@nestjs/common"; /** * Decorator that marks a class as a Nest query handler. A query handler @@ -13,10 +14,10 @@ import { v4 } from 'uuid'; * * @see https://docs.nestjs.com/recipes/cqrs#queries */ -export const QueryHandler = (query: IQuery): ClassDecorator => { +export const QueryHandler = (query: Type): ClassDecorator => { return (target: object) => { if (!Reflect.hasOwnMetadata(QUERY_METADATA, query)) { - Reflect.defineMetadata(QUERY_METADATA, { id: v4() }, query); + Reflect.defineMetadata(QUERY_METADATA, { id: v4(), name: query.name }, query); } Reflect.defineMetadata(QUERY_HANDLER_METADATA, query, target); }; diff --git a/src/interfaces/queries/query-metadata.interface.ts b/src/interfaces/queries/query-metadata.interface.ts index b60e436c..f61ee619 100644 --- a/src/interfaces/queries/query-metadata.interface.ts +++ b/src/interfaces/queries/query-metadata.interface.ts @@ -1,3 +1,4 @@ export interface QueryMetadata { id: string; + name: string; } \ No newline at end of file diff --git a/src/query-bus.ts b/src/query-bus.ts index 39666bb3..45ddd1b0 100644 --- a/src/query-bus.ts +++ b/src/query-bus.ts @@ -1,4 +1,4 @@ -import { Injectable, Type } from '@nestjs/common'; +import {Injectable, Logger, Type} from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; import 'reflect-metadata'; import { QUERY_HANDLER_METADATA, QUERY_METADATA } from './decorators/constants'; @@ -27,6 +27,8 @@ export class QueryBus { private handlers = new Map>(); private _publisher: IQueryPublisher; + private readonly _logger = new Logger(QueryBus.name); + constructor(private readonly moduleRef: ModuleRef) { super(); @@ -69,9 +71,14 @@ export class QueryBus bind( handler: IQueryHandler, - queryId: string, + { id, name }: QueryMetadata, ) { - this.handlers.set(queryId, handler); + if(this.handlers.has(id)) { + const previousHandlerName = this.handlers.get(id).constructor.name; + const handlerName = handler.constructor.name; + this._logger.warn(`Multiple handlers for query ${name}. Repleacing ${previousHandlerName} with ${handlerName}`) + } + this.handlers.set(id, handler); } register(handlers: QueryHandlerType[] = []) { @@ -83,11 +90,11 @@ export class QueryBus if (!instance) { return; } - const target = this.reflectQueryId(handler); - if (!target) { + const queryMetadata = this.reflectQueryMetadata(handler); + if (!queryMetadata) { throw new InvalidQueryHandlerException(); } - this.bind(instance as IQueryHandler, target); + this.bind(instance as IQueryHandler, queryMetadata); } private getQueryId(query: QueryBase): string { @@ -103,18 +110,17 @@ export class QueryBus return queryMetadata.id; } - private reflectQueryId( + private reflectQueryMetadata( handler: QueryHandlerType, - ): string | undefined { + ): QueryMetadata { const query: Type = Reflect.getMetadata( QUERY_HANDLER_METADATA, handler, ); - const queryMetadata: QueryMetadata = Reflect.getMetadata( + return Reflect.getMetadata( QUERY_METADATA, query, ); - return queryMetadata.id; } private useDefaultPublisher() { From 9d3f400da009fb2a18ec7adbf12a620f57c43d7d Mon Sep 17 00:00:00 2001 From: ZimGil Date: Thu, 27 Jul 2023 19:11:03 +0300 Subject: [PATCH 2/2] feat(logs): log duplicate command handlers --- src/command-bus.ts | 29 ++++++++++++------- src/decorators/command-handler.decorator.ts | 9 ++++-- src/decorators/query-handler.decorator.ts | 8 +++-- .../commands/command-metadata.interface.ts | 3 +- .../queries/query-metadata.interface.ts | 2 +- src/query-bus.ts | 19 ++++++------ 6 files changed, 44 insertions(+), 26 deletions(-) diff --git a/src/command-bus.ts b/src/command-bus.ts index 1f5f95bd..f3239b45 100644 --- a/src/command-bus.ts +++ b/src/command-bus.ts @@ -1,4 +1,4 @@ -import { Injectable, Type } from '@nestjs/common'; +import { Injectable, Logger, Type } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; import 'reflect-metadata'; import { @@ -26,6 +26,7 @@ export class CommandBus { private handlers = new Map>(); private _publisher: ICommandPublisher; + private readonly _logger = new Logger(CommandBus.name); constructor(private readonly moduleRef: ModuleRef) { super(); @@ -64,7 +65,17 @@ export class CommandBus return handler.execute(command); } - bind(handler: ICommandHandler, id: string) { + bind( + handler: ICommandHandler, + { id, name }: CommandMetadata, + ) { + if (this.handlers.has(id)) { + const previousHandlerName = this.handlers.get(id).constructor.name; + const handlerName = handler.constructor.name; + this._logger.warn( + `Multiple handlers for command ${name}. Repleacing ${previousHandlerName} with ${handlerName}.`, + ); + } this.handlers.set(id, handler); } @@ -77,11 +88,11 @@ export class CommandBus if (!instance) { return; } - const target = this.reflectCommandId(handler); - if (!target) { + const commandMetadata = this.reflectCommandMetadata(handler); + if (!commandMetadata) { throw new InvalidCommandHandlerException(); } - this.bind(instance as ICommandHandler, target); + this.bind(instance as ICommandHandler, commandMetadata); } private getCommandId(command: CommandBase): string { @@ -97,16 +108,12 @@ export class CommandBus return commandMetadata.id; } - private reflectCommandId(handler: CommandHandlerType): string | undefined { + private reflectCommandMetadata(handler: CommandHandlerType): CommandMetadata { const command: Type = Reflect.getMetadata( COMMAND_HANDLER_METADATA, handler, ); - const commandMetadata: CommandMetadata = Reflect.getMetadata( - COMMAND_METADATA, - command, - ); - return commandMetadata.id; + return Reflect.getMetadata(COMMAND_METADATA, command); } private useDefaultPublisher() { diff --git a/src/decorators/command-handler.decorator.ts b/src/decorators/command-handler.decorator.ts index 8490830f..8a89a3f7 100644 --- a/src/decorators/command-handler.decorator.ts +++ b/src/decorators/command-handler.decorator.ts @@ -2,6 +2,7 @@ import 'reflect-metadata'; import { ICommand } from '../index'; import { COMMAND_HANDLER_METADATA, COMMAND_METADATA } from './constants'; import { v4 } from 'uuid'; +import { Type } from '@nestjs/common'; /** * Decorator that marks a class as a Nest command handler. A command handler @@ -13,10 +14,14 @@ import { v4 } from 'uuid'; * * @see https://docs.nestjs.com/recipes/cqrs#commands */ -export const CommandHandler = (command: ICommand | (new (...args: any[]) => ICommand)): ClassDecorator => { +export const CommandHandler = (command: Type): ClassDecorator => { return (target: object) => { if (!Reflect.hasOwnMetadata(COMMAND_METADATA, command)) { - Reflect.defineMetadata(COMMAND_METADATA, { id: v4() }, command); + Reflect.defineMetadata( + COMMAND_METADATA, + { id: v4(), name: command.name }, + command, + ); } Reflect.defineMetadata(COMMAND_HANDLER_METADATA, command, target); }; diff --git a/src/decorators/query-handler.decorator.ts b/src/decorators/query-handler.decorator.ts index 589d3f0c..2c1f077c 100644 --- a/src/decorators/query-handler.decorator.ts +++ b/src/decorators/query-handler.decorator.ts @@ -2,7 +2,7 @@ import 'reflect-metadata'; import { IQuery } from '../interfaces'; import { QUERY_HANDLER_METADATA, QUERY_METADATA } from './constants'; import { v4 } from 'uuid'; -import { Type } from "@nestjs/common"; +import { Type } from '@nestjs/common'; /** * Decorator that marks a class as a Nest query handler. A query handler @@ -17,7 +17,11 @@ import { Type } from "@nestjs/common"; export const QueryHandler = (query: Type): ClassDecorator => { return (target: object) => { if (!Reflect.hasOwnMetadata(QUERY_METADATA, query)) { - Reflect.defineMetadata(QUERY_METADATA, { id: v4(), name: query.name }, query); + Reflect.defineMetadata( + QUERY_METADATA, + { id: v4(), name: query.name }, + query, + ); } Reflect.defineMetadata(QUERY_HANDLER_METADATA, query, target); }; diff --git a/src/interfaces/commands/command-metadata.interface.ts b/src/interfaces/commands/command-metadata.interface.ts index 80f4b377..7379b0d3 100644 --- a/src/interfaces/commands/command-metadata.interface.ts +++ b/src/interfaces/commands/command-metadata.interface.ts @@ -1,3 +1,4 @@ export interface CommandMetadata { id: string; -} \ No newline at end of file + name: string; +} diff --git a/src/interfaces/queries/query-metadata.interface.ts b/src/interfaces/queries/query-metadata.interface.ts index f61ee619..039f7735 100644 --- a/src/interfaces/queries/query-metadata.interface.ts +++ b/src/interfaces/queries/query-metadata.interface.ts @@ -1,4 +1,4 @@ export interface QueryMetadata { id: string; name: string; -} \ No newline at end of file +} diff --git a/src/query-bus.ts b/src/query-bus.ts index 45ddd1b0..76ac1738 100644 --- a/src/query-bus.ts +++ b/src/query-bus.ts @@ -1,4 +1,4 @@ -import {Injectable, Logger, Type} from '@nestjs/common'; +import { Injectable, Logger, Type } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; import 'reflect-metadata'; import { QUERY_HANDLER_METADATA, QUERY_METADATA } from './decorators/constants'; @@ -29,7 +29,6 @@ export class QueryBus private _publisher: IQueryPublisher; private readonly _logger = new Logger(QueryBus.name); - constructor(private readonly moduleRef: ModuleRef) { super(); this.useDefaultPublisher(); @@ -73,10 +72,12 @@ export class QueryBus handler: IQueryHandler, { id, name }: QueryMetadata, ) { - if(this.handlers.has(id)) { + if (this.handlers.has(id)) { const previousHandlerName = this.handlers.get(id).constructor.name; const handlerName = handler.constructor.name; - this._logger.warn(`Multiple handlers for query ${name}. Repleacing ${previousHandlerName} with ${handlerName}`) + this._logger.warn( + `Multiple handlers for query ${name}. Repleacing ${previousHandlerName} with ${handlerName}.`, + ); } this.handlers.set(id, handler); } @@ -94,7 +95,10 @@ export class QueryBus if (!queryMetadata) { throw new InvalidQueryHandlerException(); } - this.bind(instance as IQueryHandler, queryMetadata); + this.bind( + instance as IQueryHandler, + queryMetadata, + ); } private getQueryId(query: QueryBase): string { @@ -117,10 +121,7 @@ export class QueryBus QUERY_HANDLER_METADATA, handler, ); - return Reflect.getMetadata( - QUERY_METADATA, - query, - ); + return Reflect.getMetadata(QUERY_METADATA, query); } private useDefaultPublisher() {