Skip to content

Commit

Permalink
Merge branch 'chore/improve-comments'
Browse files Browse the repository at this point in the history
  • Loading branch information
kamilmysliwiec committed Jun 22, 2023
2 parents b6b4732 + 0fae7aa commit 4351a21
Show file tree
Hide file tree
Showing 13 changed files with 169 additions and 13 deletions.
49 changes: 47 additions & 2 deletions src/aggregate-root.ts
Original file line number Diff line number Diff line change
@@ -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<EventBase extends IEvent = IEvent> {
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<T extends EventBase = EventBase>(event: T) {}

publishAll<T extends EventBase = EventBase>(event: T[]) {}
/**
* Publishes multiple events. Must be merged with the publisher context in order to work.
* @param events The events to publish.
*/
publishAll<T extends EventBase = EventBase>(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<T extends EventBase = EventBase>(event: T, isFromHistory = false) {
if (!isFromHistory && !this.autoCommit) {
this[INTERNAL_EVENTS].push(event);
Expand Down
24 changes: 21 additions & 3 deletions src/command-bus.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -10,7 +13,7 @@ import {
ICommand,
ICommandBus,
ICommandHandler,
ICommandPublisher
ICommandPublisher,
} from './interfaces/index';
import { ObservableBus } from './utils/observable-bus';

Expand All @@ -19,7 +22,8 @@ export type CommandHandlerType = Type<ICommandHandler<ICommand>>;
@Injectable()
export class CommandBus<CommandBase extends ICommand = ICommand>
extends ObservableBus<CommandBase>
implements ICommandBus<CommandBase> {
implements ICommandBus<CommandBase>
{
private handlers = new Map<string, ICommandHandler<CommandBase>>();
private _publisher: ICommandPublisher<CommandBase>;

Expand All @@ -28,14 +32,28 @@ export class CommandBus<CommandBase extends ICommand = ICommand>
this.useDefaultPublisher();
}

/**
* Returns the publisher.
* Default publisher is `DefaultCommandPubSub` (in memory).
*/
get publisher(): ICommandPublisher<CommandBase> {
return this._publisher;
}

/**
* Sets the publisher.
* Default publisher is `DefaultCommandPubSub` (in memory).
* @param _publisher The publisher to set.
*/
set publisher(_publisher: ICommandPublisher<CommandBase>) {
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<T extends CommandBase, R = any>(command: T): Promise<R> {
const commandId = this.getCommandId(command);
const handler = this.handlers.get(commandId);
Expand Down
17 changes: 17 additions & 0 deletions src/event-bus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,19 @@ export class EventBus<EventBase extends IEvent = IEvent>
this.useDefaultPublisher();
}

/**
* Returns the publisher.
* Default publisher is `DefaultPubSub` (in memory).
*/
get publisher(): IEventPublisher<EventBase> {
return this._publisher;
}

/**
* Sets the publisher.
* Default publisher is `DefaultPubSub` (in memory).
* @param _publisher The publisher to set.
*/
set publisher(_publisher: IEventPublisher<EventBase>) {
this._publisher = _publisher;
}
Expand All @@ -60,10 +69,18 @@ export class EventBus<EventBase extends IEvent = IEvent>
this.subscriptions.forEach((subscription) => subscription.unsubscribe());
}

/**
* Publishes an event.
* @param event The event to publish.
*/
publish<T extends EventBase>(event: T) {
return this._publisher.publish(event);
}

/**
* Publishes multiple events.
* @param events The events to publish.
*/
publishAll<T extends EventBase>(events: T[]) {
if (this._publisher.publishAll) {
return this._publisher.publishAll(events);
Expand Down
12 changes: 11 additions & 1 deletion src/event-publisher.ts
Original file line number Diff line number Diff line change
@@ -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<T> {
Expand All @@ -11,6 +11,11 @@ export interface Constructor<T> {
export class EventPublisher<EventBase extends IEvent = IEvent> {
constructor(private readonly eventBus: EventBus<EventBase>) {}

/**
* 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<T extends Constructor<AggregateRoot<EventBase>>>(
metatype: T,
): T {
Expand All @@ -26,6 +31,11 @@ export class EventPublisher<EventBase extends IEvent = IEvent> {
};
}

/**
* 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<T extends AggregateRoot<EventBase>>(object: T): T {
const eventBus = this.eventBus;
object.publish = (event: EventBase) => {
Expand Down
7 changes: 7 additions & 0 deletions src/interfaces/commands/command-bus.interface.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { ICommand } from './command.interface';

/**
* Represents a command bus.
*/
export interface ICommandBus<CommandBase extends ICommand = ICommand> {
/**
* Executes a command.
* @param command The command to execute.
*/
execute<T extends CommandBase, R = any>(command: T): Promise<R>;
}
10 changes: 9 additions & 1 deletion src/interfaces/commands/command-handler.interface.ts
Original file line number Diff line number Diff line change
@@ -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<TResult>;
}
12 changes: 12 additions & 0 deletions src/interfaces/events/event-bus.interface.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
import { IEvent } from './event.interface';

/**
* Represents an event bus.
*/
export interface IEventBus<EventBase extends IEvent = IEvent> {
/**
* Publishes an event.
* @param event The event to publish.
*/
publish<T extends EventBase>(event: T);

/**
* Publishes multiple events.
* @param events The events to publish.
*/
publishAll(events: EventBase[]);
}
7 changes: 7 additions & 0 deletions src/interfaces/events/event-handler.interface.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { IEvent } from './event.interface';

/**
* Represents an event handler.
*/
export interface IEventHandler<T extends IEvent = any> {
/**
* Handles an event.
* @param event The event to handle.
*/
handle(event: T): any;
}
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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;
}
7 changes: 7 additions & 0 deletions src/interfaces/queries/query-bus.interface.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { IQuery } from './query.interface';

/**
* Represents a query bus.
*/
export interface IQueryBus<QueryBase extends IQuery = IQuery> {
/**
* Executes a query.
* @param query The query to execute.
*/
execute<T extends QueryBase = QueryBase, TRes = any>(query: T): Promise<TRes>;
}
7 changes: 7 additions & 0 deletions src/interfaces/queries/query-handler.interface.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { IQuery } from './query.interface';

/**
* Represents a query handler.
*/
export interface IQueryHandler<T extends IQuery = any, TRes = any> {
/**
* Executes a query.
* @param query The query to execute.
*/
execute(query: T): Promise<TRes>;
}
2 changes: 1 addition & 1 deletion src/interfaces/saga.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<EventBase>) => Observable<CommandBase>;
23 changes: 19 additions & 4 deletions src/query-bus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<QueryBase extends IQuery = IQuery,
QueryResultBase extends IQueryResult = IQueryResult> = Type<IQueryHandler<QueryBase, QueryResultBase>>;
export type QueryHandlerType<
QueryBase extends IQuery = IQuery,
QueryResultBase extends IQueryResult = IQueryResult,
> = Type<IQueryHandler<QueryBase, QueryResultBase>>;

@Injectable()
export class QueryBus<QueryBase extends IQuery = IQuery>
extends ObservableBus<QueryBase>
implements IQueryBus<QueryBase> {
implements IQueryBus<QueryBase>
{
private handlers = new Map<string, IQueryHandler<QueryBase, IQueryResult>>();
private _publisher: IQueryPublisher<QueryBase>;

Expand All @@ -30,14 +33,26 @@ export class QueryBus<QueryBase extends IQuery = IQuery>
this.useDefaultPublisher();
}

/**
* Returns the publisher.
*/
get publisher(): IQueryPublisher<QueryBase> {
return this._publisher;
}

/**
* Sets the publisher.
* Default publisher is `DefaultQueryPubSub` (in memory).
* @param _publisher The publisher to set.
*/
set publisher(_publisher: IQueryPublisher<QueryBase>) {
this._publisher = _publisher;
}

/**
* Executes a query.
* @param query The query to execute.
*/
async execute<T extends QueryBase, TResult = any>(
query: T,
): Promise<TResult> {
Expand Down

0 comments on commit 4351a21

Please sign in to comment.