From 0fae7aa939b3dc9f8e18d8fcf02678a7ebc21dfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20My=C5=9Bliwiec?= Date: Thu, 22 Jun 2023 14:12:25 +0200 Subject: [PATCH] docs: improve comments --- src/aggregate-root.ts | 49 ++++++++++++++++++- src/command-bus.ts | 24 +++++++-- src/event-bus.ts | 17 +++++++ src/event-publisher.ts | 12 ++++- .../commands/command-bus.interface.ts | 7 +++ .../commands/command-handler.interface.ts | 10 +++- src/interfaces/events/event-bus.interface.ts | 12 +++++ .../events/event-handler.interface.ts | 7 +++ .../unhandled-exception-info.interface.ts | 5 +- src/interfaces/queries/query-bus.interface.ts | 7 +++ .../queries/query-handler.interface.ts | 7 +++ src/interfaces/saga.type.ts | 2 +- src/query-bus.ts | 23 +++++++-- 13 files changed, 169 insertions(+), 13 deletions(-) diff --git a/src/aggregate-root.ts b/src/aggregate-root.ts index 38f1bc3c..aab556a9 100644 --- a/src/aggregate-root.ts +++ b/src/aggregate-root.ts @@ -1,44 +1,89 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-empty-function */ -import { IEvent, IEventHandler } from './interfaces'; import { Type } from '@nestjs/common'; +import { IEvent, IEventHandler } from './interfaces'; const INTERNAL_EVENTS = Symbol(); const IS_AUTO_COMMIT_ENABLED = Symbol(); +/** + * Represents an aggregate root. + * An aggregate root is an entity that represents a meaningful concept in the domain. + * It is the root of an aggregate, which is a cluster of domain objects that can be treated as a single unit. + * + * @template EventBase The base type of the events. + */ export abstract class AggregateRoot { public [IS_AUTO_COMMIT_ENABLED] = false; private readonly [INTERNAL_EVENTS]: EventBase[] = []; + /** + * Sets whether the aggregate root should automatically commit events. + */ set autoCommit(value: boolean) { this[IS_AUTO_COMMIT_ENABLED] = value; } + /** + * Gets whether the aggregate root should automatically commit events. + */ get autoCommit(): boolean { return this[IS_AUTO_COMMIT_ENABLED]; } + /** + * Publishes an event. Must be merged with the publisher context in order to work. + * @param event The event to publish. + */ publish(event: T) {} - publishAll(event: T[]) {} + /** + * Publishes multiple events. Must be merged with the publisher context in order to work. + * @param events The events to publish. + */ + publishAll(events: T[]) {} + /** + * Commits all uncommitted events. + */ commit() { this.publishAll(this[INTERNAL_EVENTS]); this[INTERNAL_EVENTS].length = 0; } + /** + * Uncommits all events. + */ uncommit() { this[INTERNAL_EVENTS].length = 0; } + /** + * Returns all uncommitted events. + * @returns All uncommitted events. + */ getUncommittedEvents(): EventBase[] { return this[INTERNAL_EVENTS]; } + /** + * Loads events from history. + * @param history The history to load. + */ loadFromHistory(history: EventBase[]) { history.forEach((event) => this.apply(event, true)); } + /** + * Applies an event. + * If auto commit is enabled, the event will be published immediately (note: must be merged with the publisher context in order to work). + * Otherwise, the event will be stored in the internal events array, and will be published when the commit method is called. + * Also, the corresponding event handler will be called (if exists). + * For example, if the event is called UserCreatedEvent, the "onUserCreatedEvent" method will be called. + * + * @param event The event to apply. + * @param isFromHistory Whether the event is from history. + */ apply(event: T, isFromHistory = false) { if (!isFromHistory && !this.autoCommit) { this[INTERNAL_EVENTS].push(event); diff --git a/src/command-bus.ts b/src/command-bus.ts index 25a136d8..1f5f95bd 100644 --- a/src/command-bus.ts +++ b/src/command-bus.ts @@ -1,7 +1,10 @@ import { Injectable, Type } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; import 'reflect-metadata'; -import { COMMAND_HANDLER_METADATA, COMMAND_METADATA } from './decorators/constants'; +import { + COMMAND_HANDLER_METADATA, + COMMAND_METADATA, +} from './decorators/constants'; import { CommandHandlerNotFoundException } from './exceptions/command-not-found.exception'; import { DefaultCommandPubSub } from './helpers/default-command-pubsub'; import { InvalidCommandHandlerException } from './index'; @@ -10,7 +13,7 @@ import { ICommand, ICommandBus, ICommandHandler, - ICommandPublisher + ICommandPublisher, } from './interfaces/index'; import { ObservableBus } from './utils/observable-bus'; @@ -19,7 +22,8 @@ export type CommandHandlerType = Type>; @Injectable() export class CommandBus extends ObservableBus - implements ICommandBus { + implements ICommandBus +{ private handlers = new Map>(); private _publisher: ICommandPublisher; @@ -28,14 +32,28 @@ export class CommandBus this.useDefaultPublisher(); } + /** + * Returns the publisher. + * Default publisher is `DefaultCommandPubSub` (in memory). + */ get publisher(): ICommandPublisher { return this._publisher; } + /** + * Sets the publisher. + * Default publisher is `DefaultCommandPubSub` (in memory). + * @param _publisher The publisher to set. + */ set publisher(_publisher: ICommandPublisher) { this._publisher = _publisher; } + /** + * Executes a command. + * @param command The command to execute. + * @returns A promise that, when resolved, will contain the result returned by the command's handler. + */ execute(command: T): Promise { const commandId = this.getCommandId(command); const handler = this.handlers.get(commandId); diff --git a/src/event-bus.ts b/src/event-bus.ts index dc42cdea..db24d707 100644 --- a/src/event-bus.ts +++ b/src/event-bus.ts @@ -48,10 +48,19 @@ export class EventBus this.useDefaultPublisher(); } + /** + * Returns the publisher. + * Default publisher is `DefaultPubSub` (in memory). + */ get publisher(): IEventPublisher { return this._publisher; } + /** + * Sets the publisher. + * Default publisher is `DefaultPubSub` (in memory). + * @param _publisher The publisher to set. + */ set publisher(_publisher: IEventPublisher) { this._publisher = _publisher; } @@ -60,10 +69,18 @@ export class EventBus this.subscriptions.forEach((subscription) => subscription.unsubscribe()); } + /** + * Publishes an event. + * @param event The event to publish. + */ publish(event: T) { return this._publisher.publish(event); } + /** + * Publishes multiple events. + * @param events The events to publish. + */ publishAll(events: T[]) { if (this._publisher.publishAll) { return this._publisher.publishAll(events); diff --git a/src/event-publisher.ts b/src/event-publisher.ts index 532f4e91..64651622 100644 --- a/src/event-publisher.ts +++ b/src/event-publisher.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; -import { EventBus } from './event-bus'; import { AggregateRoot } from './aggregate-root'; +import { EventBus } from './event-bus'; import { IEvent } from './interfaces'; export interface Constructor { @@ -11,6 +11,11 @@ export interface Constructor { export class EventPublisher { constructor(private readonly eventBus: EventBus) {} + /** + * Merge the event publisher into the provided class. + * This is required to make `publish` and `publishAll` available on the `AgreggateRoot` class. + * @param metatype The class to merge into. + */ mergeClassContext>>( metatype: T, ): T { @@ -26,6 +31,11 @@ export class EventPublisher { }; } + /** + * Merge the event publisher into the provided object. + * This is required to make `publish` and `publishAll` available on the `AgreggateRoot` class instance. + * @param object The object to merge into. + */ mergeObjectContext>(object: T): T { const eventBus = this.eventBus; object.publish = (event: EventBase) => { diff --git a/src/interfaces/commands/command-bus.interface.ts b/src/interfaces/commands/command-bus.interface.ts index 751341db..a58677f6 100644 --- a/src/interfaces/commands/command-bus.interface.ts +++ b/src/interfaces/commands/command-bus.interface.ts @@ -1,5 +1,12 @@ import { ICommand } from './command.interface'; +/** + * Represents a command bus. + */ export interface ICommandBus { + /** + * Executes a command. + * @param command The command to execute. + */ execute(command: T): Promise; } diff --git a/src/interfaces/commands/command-handler.interface.ts b/src/interfaces/commands/command-handler.interface.ts index 6ea461b5..343f28c3 100644 --- a/src/interfaces/commands/command-handler.interface.ts +++ b/src/interfaces/commands/command-handler.interface.ts @@ -1,8 +1,16 @@ import { ICommand } from './command.interface'; +/** + * Represents a command handler. + * Command handlers are used to execute commands. + */ export interface ICommandHandler< TCommand extends ICommand = any, - TResult = any + TResult = any, > { + /** + * Executes a command. + * @param command The command to execute. + */ execute(command: TCommand): Promise; } diff --git a/src/interfaces/events/event-bus.interface.ts b/src/interfaces/events/event-bus.interface.ts index 9064bb55..bbbf367e 100644 --- a/src/interfaces/events/event-bus.interface.ts +++ b/src/interfaces/events/event-bus.interface.ts @@ -1,6 +1,18 @@ import { IEvent } from './event.interface'; +/** + * Represents an event bus. + */ export interface IEventBus { + /** + * Publishes an event. + * @param event The event to publish. + */ publish(event: T); + + /** + * Publishes multiple events. + * @param events The events to publish. + */ publishAll(events: EventBase[]); } diff --git a/src/interfaces/events/event-handler.interface.ts b/src/interfaces/events/event-handler.interface.ts index 23614fa6..69c61581 100644 --- a/src/interfaces/events/event-handler.interface.ts +++ b/src/interfaces/events/event-handler.interface.ts @@ -1,5 +1,12 @@ import { IEvent } from './event.interface'; +/** + * Represents an event handler. + */ export interface IEventHandler { + /** + * Handles an event. + * @param event The event to handle. + */ handle(event: T): any; } diff --git a/src/interfaces/exceptions/unhandled-exception-info.interface.ts b/src/interfaces/exceptions/unhandled-exception-info.interface.ts index 7b747362..07ab9050 100644 --- a/src/interfaces/exceptions/unhandled-exception-info.interface.ts +++ b/src/interfaces/exceptions/unhandled-exception-info.interface.ts @@ -1,6 +1,9 @@ import { ICommand } from '../commands/command.interface'; import { IEvent } from '../events/event.interface'; +/** + * Represents an unhandled exception. + */ export interface UnhandledExceptionInfo< Cause = IEvent | ICommand, Exception = any, @@ -10,7 +13,7 @@ export interface UnhandledExceptionInfo< */ exception: Exception; /** - * The cause of the exception. + * The cause of the exception (event or command reference). */ cause: Cause; } diff --git a/src/interfaces/queries/query-bus.interface.ts b/src/interfaces/queries/query-bus.interface.ts index b236ff46..b1ed79ce 100644 --- a/src/interfaces/queries/query-bus.interface.ts +++ b/src/interfaces/queries/query-bus.interface.ts @@ -1,5 +1,12 @@ import { IQuery } from './query.interface'; +/** + * Represents a query bus. + */ export interface IQueryBus { + /** + * Executes a query. + * @param query The query to execute. + */ execute(query: T): Promise; } diff --git a/src/interfaces/queries/query-handler.interface.ts b/src/interfaces/queries/query-handler.interface.ts index d347248b..612be8de 100644 --- a/src/interfaces/queries/query-handler.interface.ts +++ b/src/interfaces/queries/query-handler.interface.ts @@ -1,5 +1,12 @@ import { IQuery } from './query.interface'; +/** + * Represents a query handler. + */ export interface IQueryHandler { + /** + * Executes a query. + * @param query The query to execute. + */ execute(query: T): Promise; } diff --git a/src/interfaces/saga.type.ts b/src/interfaces/saga.type.ts index 8f7f987e..8eb1b228 100644 --- a/src/interfaces/saga.type.ts +++ b/src/interfaces/saga.type.ts @@ -4,5 +4,5 @@ import { IEvent } from './events/event.interface'; export type ISaga< EventBase extends IEvent = IEvent, - CommandBase extends ICommand = ICommand + CommandBase extends ICommand = ICommand, > = (events$: Observable) => Observable; diff --git a/src/query-bus.ts b/src/query-bus.ts index 8fb4127b..39666bb3 100644 --- a/src/query-bus.ts +++ b/src/query-bus.ts @@ -10,18 +10,21 @@ import { IQueryBus, IQueryHandler, IQueryPublisher, - IQueryResult + IQueryResult, } from './interfaces'; import { QueryMetadata } from './interfaces/queries/query-metadata.interface'; import { ObservableBus } from './utils/observable-bus'; -export type QueryHandlerType = Type>; +export type QueryHandlerType< + QueryBase extends IQuery = IQuery, + QueryResultBase extends IQueryResult = IQueryResult, +> = Type>; @Injectable() export class QueryBus extends ObservableBus - implements IQueryBus { + implements IQueryBus +{ private handlers = new Map>(); private _publisher: IQueryPublisher; @@ -30,14 +33,26 @@ export class QueryBus this.useDefaultPublisher(); } + /** + * Returns the publisher. + */ get publisher(): IQueryPublisher { return this._publisher; } + /** + * Sets the publisher. + * Default publisher is `DefaultQueryPubSub` (in memory). + * @param _publisher The publisher to set. + */ set publisher(_publisher: IQueryPublisher) { this._publisher = _publisher; } + /** + * Executes a query. + * @param query The query to execute. + */ async execute( query: T, ): Promise {