Skip to content

Commit

Permalink
Merge pull request #124 from 4lessandrodev/develop
Browse files Browse the repository at this point in the history
feat: change the way to add and dispatch domain events
  • Loading branch information
4lessandrodev committed Mar 18, 2024
2 parents da92fa3 + 09a26d6 commit 13c99d6
Show file tree
Hide file tree
Showing 14 changed files with 714 additions and 421 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ types.d.ts.map
types.js.map

!tests/*
!lib/*
!lib/*
.idea/
112 changes: 112 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,118 @@ All notable changes to this project will be documented in this file.

---

### [1.20.0] - 2024-03-17

### Changed

- change: modify the to add and dispatch event on aggregate
- added: Implemented a new instance of the event management class, allowing for better event handling and organization.

**Details:**
Introduced a new method to create an event management instance for the aggregate.
Improved event handling and organization, enhancing the overall performance and efficiency of event management.
Enabled easier integration and usage of event management features in various applications.

```ts

import { TsEvents } from 'rich-domain';

/**
* Create a new instance of the event management class
* Pass the aggregate as a parameter
* Return an event management instance for the aggregate
*/
const events = new TsEvents(aggregate);

events.addEvent('eventName', (...args) => {
console.log(args);
});

// dispatch all events
await events.dispatchEvents();

// OR dispatch a specific event
events.dispatchEvent('eventName');

```


Implemented a new event handling mechanism using the Handler class.
Example:

```ts

// implement extending to EventHandler
class Handler extends EventHandler<Product> {

constructor() {
super({ eventName: 'sample' });
};

dispatch(product: Product, params: [DEvent<Product>, any[]]): void | Promise<void> {
const model = product.toObject();
const [event, args] = params;

console.log(model);
console.log(event);

// custom params provided on call dispatchEvent
console.log(args);
}
}

const event = new Handler();

aggregate.addEvent(event);

aggregate.dispatchEvent('sample', { custom: 'params' });

```

### Bug Fixes
Fixed an issue with the event dispatching mechanism.
Notes
This version introduces significant changes to the event handling system, enhancing the flexibility and usability of the Aggregate class.

### Migrating from v1.19.x

Change event handler implementation


```ts
// use extends to EventHandler
class Handler extends EventHandler<Product> {

constructor() {
super({ eventName: 'sample' });
};

// aggregate as first param
dispatch(product: Product): void | Promise<void> {
// your implementation
}
}

```

Remove imports `DomainEvents` and use events from aggregate instance

```ts

aggregate.addEvent(event);

aggregate.dispatchEvent('eventName');

```



---

## Released

---

### [1.19.2] - 2024-03-15

### Changed
Expand Down
52 changes: 15 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1673,28 +1673,6 @@ export default Product;
#### Domain Event

Let's create an aggregate instance and see how to add domain event to it.

```ts

export class ProductCreatedEvent implements IHandle<Product>{
public eventName: string;

constructor() {
this.eventName = 'ProductCreated';
}

dispatch(event: IDomainEvent<Product>): void {
// your action here
const { aggregate } = event;
console.log(`EVENT DISPATCH: ${aggregate.hashCode().value()}`);
}
}

export default ProductCreatedEvent;

```

Now let's add the event to a product instance.<br>
Events are stored in memory and are deleted after being triggered.

```ts
Expand All @@ -1703,27 +1681,27 @@ const result = Product.create({ name, price });

const product = result.value();

const event = new ProductCreatedEvent();

product.addEvent(event);

```

Now we can dispatch the event whenever we want.
product.addEvent('eventName', (product) => {
console.log(product.toObject())
});

```ts

DomainEvents.dispatch({ id: product.id, eventName: "ProductCreated" });
// or alternatively you can create a event handler

> "EVENT DISPATCH: [Aggregate@Product]:6039756f-d3bc-452e-932a-ec89ff536dda"
class Handler extends EventHandler<Product> {
constructor() { super({ eventName: 'eventName' }) };

// OR you can dispatch all events in aggregate instance
dispatch(product: Product): void {
const model = product.toObject();
console.log(model);
}
}

product.dispatchEvent();
// add instance to aggregate
product.addEvent(new Handler());

// OR
// dispatch from aggregate instance

product.dispatchEvent("ProductCreated");
product.dispatchEvent("eventName");

```
---
Expand Down
95 changes: 44 additions & 51 deletions lib/core/aggregate.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { EntityProps, EventHandler, EventMetrics, IAggregate, IDomainEvent, IHandle, IReplaceOptions, IResult, ISettings, UID } from "../types";
import DomainEvent from "./domain-event";
import { EventHandler, IResult, ISettings, Options, UID } from "../types";
import { EntityProps, EventMetrics, Handler, IAggregate } from "../types";
import TsEvent from "./events";
import Entity from "./entity";
import ID from "./id";
import Result from "./result";
Expand All @@ -8,13 +9,14 @@ import Result from "./result";
* @description Aggregate identified by an id
*/
export class Aggregate<Props extends EntityProps> extends Entity<Props> implements IAggregate<Props> {
private _domainEvents: Array<IDomainEvent<IAggregate<Props>>>;
private _domainEvents: TsEvent<this>;
private _dispatchEventsAmount: number;

constructor(props: Props, config?: ISettings, events?: Array<IDomainEvent<IAggregate<Props>>>) {
constructor(props: Props, config?: ISettings, events?: TsEvent<IAggregate<Props>>) {
super(props, config);
this._dispatchEventsAmount = 0;
this._domainEvents = Array.isArray(events) ? events : [];
this._domainEvents = new TsEvent(this);
if (events) this._domainEvents = events as unknown as TsEvent<this>;
}

/**
Expand All @@ -38,8 +40,8 @@ export class Aggregate<Props extends EntityProps> extends Entity<Props> implemen
*/
get eventsMetrics(): EventMetrics {
return {
current: this._domainEvents.length,
total: this._domainEvents.length + this._dispatchEventsAmount,
current: this._domainEvents.metrics.totalEvents(),
total: this._domainEvents.metrics.totalEvents() + this._dispatchEventsAmount,
dispatch: this._dispatchEventsAmount
}
}
Expand All @@ -53,7 +55,7 @@ export class Aggregate<Props extends EntityProps> extends Entity<Props> implemen
*/
clone(props?: Partial<Props> & { copyEvents?: boolean }): this {
const _props = props ? { ...this.props, ...props } : { ...this.props };
const _events = (props && !!props.copyEvents) ? this._domainEvents : [];
const _events = (props && !!props.copyEvents) ? this._domainEvents : null;
const instance = Reflect.getPrototypeOf(this);
const args = [_props, this.config, _events];
const aggregate = Reflect.construct(instance!.constructor, args);
Expand All @@ -62,36 +64,23 @@ export class Aggregate<Props extends EntityProps> extends Entity<Props> implemen

/**
* @description Dispatch event added to aggregate instance
* @param eventName optional event name as string. If provided only event match name is called.
* @param eventName optional event name as string.
* @returns Promise void as executed event
*/
dispatchEvent(eventName?: string, handler?: EventHandler<IAggregate<any>, void>): void {
if (!eventName) return this.dispatchAll(handler);

const callback = handler || ({ execute: (): void => { } });
for (const event of this._domainEvents) {
if (event.aggregate.id.equal(this.id) && event.callback.eventName === eventName) {
this._dispatchEventsAmount++;
event.callback.dispatch(event, callback);
this.deleteEvent(eventName!);
}
}
dispatchEvent(eventName: string, ...args: any[]): void | Promise<void> {
this._domainEvents.dispatchEvent(eventName, args);
this._dispatchEventsAmount++;
}

/**
* @description Dispatch all events for current aggregate.
* @param handler as EventHandler.
* @returns promise void.
*/
dispatchAll(handler?: EventHandler<this, void>) {
const callback = handler || ({ execute: (): void => { } });
for (const event of this._domainEvents) {
if (event.aggregate.id.equal(this.id)) {
this._dispatchEventsAmount++;
event.callback.dispatch(event, callback);
}
}
this._domainEvents = [];
async dispatchAll(): Promise<void> {
const current = this._domainEvents.metrics.totalEvents();
await this._domainEvents.dispatchEvents();
this._dispatchEventsAmount = this._dispatchEventsAmount + current;
};

/**
Expand All @@ -100,39 +89,43 @@ export class Aggregate<Props extends EntityProps> extends Entity<Props> implemen
* @returns void.
*/
clearEvents(config = { resetMetrics: false }): void {
this._dispatchEventsAmount = config.resetMetrics ? 0 : this._dispatchEventsAmount;
this._domainEvents = [];
if (config.resetMetrics) this._dispatchEventsAmount = 0;
this._domainEvents.clearEvents();
};

/**
* @description Add event to aggregate instance.
* @param eventToAdd Event to be dispatched.
* @param replace 'REPLACE_DUPLICATED' option to remove old event with the same name and id.
* @emits dispatch to aggregate instance. Do not use event using global event manager as DomainEvent.dispatch
* Adds a new event.
* @param event - The event object containing the event name, handler, and options.
*/
addEvent(eventToAdd: IHandle<this>, replace?: IReplaceOptions): void {
const doReplace = replace === 'REPLACE_DUPLICATED';
const event = new DomainEvent(this, eventToAdd);
const target = Reflect.getPrototypeOf(event.callback);
const eventName = event.callback?.eventName ?? target?.constructor.name as string;
event.callback.eventName = eventName;
if (!!doReplace) this.deleteEvent(eventName);
this._domainEvents.push(event);
}
addEvent(event: EventHandler<this>): void;

/**
* Adds a new event.
* @param eventName - The name of the event.
* @param handler - The event handler function.
* @param options - The options for the event.
*/
addEvent(eventName: string, handler: Handler<this>, options?: Options): void;

addEvent(eventNameOrEvent: string | EventHandler<this>, handler?: Handler<this>, options?: Options): void {
if (typeof eventNameOrEvent === 'string' && handler) {
this._domainEvents.addEvent(eventNameOrEvent, handler! ?? null, options);
return;
}
const _options = (eventNameOrEvent as EventHandler<this>)?.params?.options;
const eventName = (eventNameOrEvent as EventHandler<this>)?.params?.eventName;
const eventHandler = (eventNameOrEvent as EventHandler<this>)?.dispatch;
this._domainEvents.addEvent(eventName, eventHandler! ?? null, _options);
}
/**
* @description Delete event match with provided name
* @param eventName event name as string
* @returns number of deleted events
*/
deleteEvent(eventName: string): number {
let deletedEventsAmount = this._domainEvents.length;

this._domainEvents = this._domainEvents.filter(
domainEvent => (domainEvent.callback.eventName !== eventName)
);

return deletedEventsAmount - this._domainEvents.length;
const totalBefore = this._domainEvents.metrics.totalEvents();
this._domainEvents.removeEvent(eventName);
return totalBefore - this._domainEvents.metrics.totalEvents();
}

public static create(props: any): IResult<any, any, any>;
Expand Down
17 changes: 0 additions & 17 deletions lib/core/domain-event.ts

This file was deleted.

0 comments on commit 13c99d6

Please sign in to comment.