137 lines
3.8 KiB
TypeScript
137 lines
3.8 KiB
TypeScript
// https://khalilstemmler.com/articles/typescript-domain-driven-design/chain-business-logic-domain-events/
|
|
|
|
import { AggregateRoot } from "../aggregate-root";
|
|
import { UniqueID } from "../value-objects";
|
|
import { IDomainEvent } from "./domain-event.interface";
|
|
|
|
// biome-ignore lint/complexity/noStaticOnlyClass: <explanation>
|
|
export class DomainEvents {
|
|
private static handlersMap: { [key: string]: Array<(event: IDomainEvent) => void> } = {};
|
|
private static markedAggregates: AggregateRoot<any>[] = [];
|
|
|
|
/**
|
|
* @method markAggregateForDispatch
|
|
* @static
|
|
* @desc Called by aggregate root objects that have created domain
|
|
* events to eventually be dispatched when the infrastructure commits
|
|
* the unit of work.
|
|
*/
|
|
|
|
public static markAggregateForDispatch(aggregate: AggregateRoot<any>): void {
|
|
const aggregateFound = !!DomainEvents.findMarkedAggregateByID(aggregate.id);
|
|
|
|
if (!aggregateFound) {
|
|
DomainEvents.markedAggregates.push(aggregate);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @method dispatchAggregateEvents
|
|
* @static
|
|
* @private
|
|
* @desc Call all of the handlers for any domain events on this aggregate.
|
|
*/
|
|
|
|
private static dispatchAggregateEvents(aggregate: AggregateRoot<any>): void {
|
|
aggregate.domainEvents.forEach((event: IDomainEvent) => DomainEvents.dispatch(event));
|
|
}
|
|
|
|
/**
|
|
* @method removeAggregateFromMarkedDispatchList
|
|
* @static
|
|
* @desc Removes an aggregate from the marked list.
|
|
*/
|
|
|
|
private static removeAggregateFromMarkedDispatchList(aggregate: AggregateRoot<any>): void {
|
|
const index = DomainEvents.markedAggregates.findIndex((a) => a.equals(aggregate));
|
|
|
|
DomainEvents.markedAggregates.splice(index, 1);
|
|
}
|
|
|
|
/**
|
|
* @method findMarkedAggregateByID
|
|
* @static
|
|
* @desc Finds an aggregate within the list of marked aggregates.
|
|
*/
|
|
|
|
private static findMarkedAggregateByID(id: UniqueID): AggregateRoot<any> {
|
|
let found!: AggregateRoot<any>;
|
|
|
|
for (const aggregate of DomainEvents.markedAggregates) {
|
|
if (aggregate.id.equals(id)) {
|
|
found = aggregate;
|
|
}
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
/**
|
|
* @method dispatchEventsForAggregate
|
|
* @static
|
|
* @desc When all we know is the ID of the aggregate, call this
|
|
* in order to dispatch any handlers subscribed to events on the
|
|
* aggregate.
|
|
*/
|
|
|
|
public static dispatchEventsForAggregate(id: UniqueID): void {
|
|
const aggregate = DomainEvents.findMarkedAggregateByID(id);
|
|
|
|
if (aggregate) {
|
|
DomainEvents.dispatchAggregateEvents(aggregate);
|
|
aggregate.clearDomainEvents();
|
|
DomainEvents.removeAggregateFromMarkedDispatchList(aggregate);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @method register
|
|
* @static
|
|
* @desc Register a handler to a domain event.
|
|
*/
|
|
|
|
public static register(callback: (event: IDomainEvent) => void, eventClassName: string): void {
|
|
if (!Object.prototype.hasOwnProperty.call(DomainEvents.handlersMap, eventClassName)) {
|
|
DomainEvents.handlersMap[eventClassName] = [];
|
|
}
|
|
DomainEvents.handlersMap[eventClassName].push(callback);
|
|
}
|
|
|
|
/**
|
|
* @method clearHandlers
|
|
* @static
|
|
* @desc Useful for testing.
|
|
*/
|
|
|
|
public static clearHandlers(): void {
|
|
DomainEvents.handlersMap = {};
|
|
}
|
|
|
|
/**
|
|
* @method clearMarkedAggregates
|
|
* @static
|
|
* @desc Useful for testing.
|
|
*/
|
|
|
|
public static clearMarkedAggregates(): void {
|
|
DomainEvents.markedAggregates = [];
|
|
}
|
|
|
|
/**
|
|
* @method dispatch
|
|
* @static
|
|
* @desc Invokes all of the subscribers to a particular domain event.
|
|
*/
|
|
|
|
private static dispatch(event: IDomainEvent): void {
|
|
const eventClassName: string = event.constructor.name;
|
|
|
|
if (Object.hasOwn(DomainEvents.handlersMap, eventClassName)) {
|
|
const handlers: any[] = DomainEvents.handlersMap[eventClassName];
|
|
for (const handler of handlers) {
|
|
handler(event);
|
|
}
|
|
}
|
|
}
|
|
}
|