Uecko_ERP/packages/rdx-ddd/src/events/domain-event.ts

137 lines
3.8 KiB
TypeScript
Raw Normal View History

2025-02-01 21:48:13 +00:00
// https://khalilstemmler.com/articles/typescript-domain-driven-design/chain-business-logic-domain-events/
import { AggregateRoot } from "../aggregate-root";
2025-05-09 10:45:32 +00:00
import { UniqueID } from "../value-objects";
2025-02-01 21:48:13 +00:00
import { IDomainEvent } from "./domain-event.interface";
2025-05-09 10:45:32 +00:00
// biome-ignore lint/complexity/noStaticOnlyClass: <explanation>
2025-02-01 21:48:13 +00:00
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 {
2025-05-09 10:45:32 +00:00
const aggregateFound = !!DomainEvents.findMarkedAggregateByID(aggregate.id);
2025-02-01 21:48:13 +00:00
if (!aggregateFound) {
2025-05-09 10:45:32 +00:00
DomainEvents.markedAggregates.push(aggregate);
2025-02-01 21:48:13 +00:00
}
}
/**
* @method dispatchAggregateEvents
* @static
* @private
* @desc Call all of the handlers for any domain events on this aggregate.
*/
private static dispatchAggregateEvents(aggregate: AggregateRoot<any>): void {
2025-05-09 10:45:32 +00:00
aggregate.domainEvents.forEach((event: IDomainEvent) => DomainEvents.dispatch(event));
2025-02-01 21:48:13 +00:00
}
/**
* @method removeAggregateFromMarkedDispatchList
* @static
* @desc Removes an aggregate from the marked list.
*/
private static removeAggregateFromMarkedDispatchList(aggregate: AggregateRoot<any>): void {
2025-05-09 10:45:32 +00:00
const index = DomainEvents.markedAggregates.findIndex((a) => a.equals(aggregate));
2025-02-01 21:48:13 +00:00
2025-05-09 10:45:32 +00:00
DomainEvents.markedAggregates.splice(index, 1);
2025-02-01 21:48:13 +00:00
}
/**
* @method findMarkedAggregateByID
* @static
* @desc Finds an aggregate within the list of marked aggregates.
*/
private static findMarkedAggregateByID(id: UniqueID): AggregateRoot<any> {
let found!: AggregateRoot<any>;
2025-05-09 10:45:32 +00:00
for (const aggregate of DomainEvents.markedAggregates) {
2025-02-01 21:48:13 +00:00
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 {
2025-05-09 10:45:32 +00:00
const aggregate = DomainEvents.findMarkedAggregateByID(id);
2025-02-01 21:48:13 +00:00
if (aggregate) {
2025-05-09 10:45:32 +00:00
DomainEvents.dispatchAggregateEvents(aggregate);
2025-02-01 21:48:13 +00:00
aggregate.clearDomainEvents();
2025-05-09 10:45:32 +00:00
DomainEvents.removeAggregateFromMarkedDispatchList(aggregate);
2025-02-01 21:48:13 +00:00
}
}
/**
* @method register
* @static
* @desc Register a handler to a domain event.
*/
public static register(callback: (event: IDomainEvent) => void, eventClassName: string): void {
2025-05-09 10:45:32 +00:00
if (!Object.prototype.hasOwnProperty.call(DomainEvents.handlersMap, eventClassName)) {
DomainEvents.handlersMap[eventClassName] = [];
2025-02-01 21:48:13 +00:00
}
2025-05-09 10:45:32 +00:00
DomainEvents.handlersMap[eventClassName].push(callback);
2025-02-01 21:48:13 +00:00
}
/**
* @method clearHandlers
* @static
* @desc Useful for testing.
*/
public static clearHandlers(): void {
2025-05-09 10:45:32 +00:00
DomainEvents.handlersMap = {};
2025-02-01 21:48:13 +00:00
}
/**
* @method clearMarkedAggregates
* @static
* @desc Useful for testing.
*/
public static clearMarkedAggregates(): void {
2025-05-09 10:45:32 +00:00
DomainEvents.markedAggregates = [];
2025-02-01 21:48:13 +00:00
}
/**
* @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;
2025-05-09 10:45:32 +00:00
if (Object.hasOwn(DomainEvents.handlersMap, eventClassName)) {
const handlers: any[] = DomainEvents.handlersMap[eventClassName];
2025-05-04 20:06:57 +00:00
for (const handler of handlers) {
2025-02-01 21:48:13 +00:00
handler(event);
}
}
}
}