Skip to content

Commit

Permalink
feat: add basic domain-events
Browse files Browse the repository at this point in the history
  • Loading branch information
Gi-jutsu committed Nov 16, 2024
1 parent 4d944f5 commit 1364083
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 3 deletions.
17 changes: 17 additions & 0 deletions src/core/primitives/aggregate-root.base.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { randomUUID } from "node:crypto";
import { DomainEvent } from "./domain-event.base.js";

interface AggregateRootProperties {
id: string;
Expand All @@ -16,6 +17,8 @@ export abstract class AggregateRoot<
> {
public readonly id: AggregateRootProperties["id"];
private readonly _properties: Properties;
// @TODO: Benchmark the performance on high-scale events (consider using a queue)
private readonly _domainEvents: DomainEvent[] = [];

constructor({ id, properties }: CreateAggregateRootProperties<Properties>) {
this.id = id ?? randomUUID();
Expand All @@ -29,10 +32,24 @@ export abstract class AggregateRoot<
return new this(...args);
}

protected commit(event: DomainEvent) {
this._domainEvents.push(event);
}

private clearDomainEvents() {
this._domainEvents.length = 0;
}

get properties() {
return Object.freeze({
id: this.id,
...this._properties,
});
}

pullDomainEvents() {
const domainEvents = [...this._domainEvents];
this.clearDomainEvents();
return domainEvents;
}
}
24 changes: 24 additions & 0 deletions src/core/primitives/domain-event.base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { randomUUID } from "node:crypto";

interface CreateDomainEventProps<Payload = Record<string, unknown>> {
aggregateId: string;
payload: Payload;
}

export abstract class DomainEvent<Payload = Record<string, unknown>> {
readonly id: string;
readonly metadata: {
// @TODO: Implement 'correlationId' in the future
// correlationId: string;
emittedByAggregateId: string;
};
readonly payload: Payload;

constructor(properties: CreateDomainEventProps<Payload>) {
this.id = randomUUID();
this.metadata = {
emittedByAggregateId: properties.aggregateId,
};
this.payload = properties.payload;
}
}
12 changes: 11 additions & 1 deletion src/identity-and-access/domain/account/aggregate-root.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { AggregateRoot } from "@core/primitives/aggregate-root.base.js";
import { NewAccountRegisteredDomainEvent } from "./events/new-account-registered.js";

interface Properties {
email: string;
Expand All @@ -7,6 +8,15 @@ interface Properties {

export class Account extends AggregateRoot<Properties> {
static create(properties: Properties) {
return new Account({ properties });
const account = new Account({ properties });

account.commit(
new NewAccountRegisteredDomainEvent({
aggregateId: account.id,
payload: account.properties,
})
);

return account;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { DomainEvent } from "@core/primitives/domain-event.base.js";

export class NewAccountRegisteredDomainEvent extends DomainEvent<{
email: string;
}> {}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { AggregateRoot } from "@core/primitives/aggregate-root.base.js";
import { DateTime } from "luxon";
import { PasswordResetRequestedDomainEvent } from "./events/password-reset-requested.domain-event.js";

interface Properties {
accountId: string;
Expand All @@ -14,13 +15,21 @@ interface CreateProperties {
export class PasswordResetRequest extends AggregateRoot<Properties> {
static create(properties: CreateProperties) {
const token = Math.random().toString(36).slice(2);

return new PasswordResetRequest({
const passwordResetRequest = new PasswordResetRequest({
properties: {
accountId: properties.accountId,
token,
expiresAt: DateTime.now().plus({ days: 1 }),
},
});

passwordResetRequest.commit(
new PasswordResetRequestedDomainEvent({
aggregateId: passwordResetRequest.id,
payload: passwordResetRequest.properties,
})
);

return passwordResetRequest;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { DomainEvent } from "@core/primitives/domain-event.base.js";
import { DateTime } from "luxon";

export class PasswordResetRequestedDomainEvent extends DomainEvent<{
accountId: string;
expiresAt: DateTime;
token: string;
}> {}

0 comments on commit 1364083

Please sign in to comment.