Uecko_ERP/packages/rdx-ddd/src/events/domain-event.ts
2025-05-09 12:45:32 +02:00

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);
}
}
}
}