@pocket-architect/core 0.1.18 → 0.1.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +102 -0
- package/build/AggregateRoot.d.ts +0 -5
- package/build/AggregateRoot.js +1 -11
- package/build/AggregateRoot.js.map +1 -1
- package/build/DomainEvent.d.ts +4 -8
- package/build/DomainEvent.js +8 -4
- package/build/DomainEvent.js.map +1 -1
- package/build/Entity.d.ts +4 -0
- package/build/Entity.js +25 -0
- package/build/Entity.js.map +1 -1
- package/build/Entity.spec.js +26 -3
- package/build/Entity.spec.js.map +1 -1
- package/build/EntityId.d.ts +2 -2
- package/build/EntityId.js.map +1 -1
- package/build/EntityId.spec.js +6 -5
- package/build/EntityId.spec.js.map +1 -1
- package/build/EventBus.d.ts +2 -0
- package/build/EventBus.spec.d.ts +1 -0
- package/build/EventBus.spec.js +157 -0
- package/build/EventBus.spec.js.map +1 -0
- package/build/eventBus/InMemoryEventBus.d.ts +4 -1
- package/build/eventBus/InMemoryEventBus.js +36 -9
- package/build/eventBus/InMemoryEventBus.js.map +1 -1
- package/build/eventBus/InMemoryEventBus.spec.js +0 -3
- package/build/eventBus/InMemoryEventBus.spec.js.map +1 -1
- package/package.json +1 -1
- package/src/AggregateRoot.ts +0 -15
- package/src/DomainEvent.ts +10 -13
- package/src/Entity.spec.ts +23 -5
- package/src/Entity.ts +29 -0
- package/src/EntityId.spec.ts +5 -4
- package/src/EntityId.ts +3 -3
- package/src/EventBus.spec.ts +108 -0
- package/src/EventBus.ts +2 -0
- package/src/eventBus/InMemoryEventBus.spec.ts +1 -5
- package/src/eventBus/InMemoryEventBus.ts +15 -3
- package/src/AggregateRoot.spec.ts +0 -35
package/README.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
## Entity
|
|
2
|
+
|
|
3
|
+
### Snapshots
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
// Створює копію властивостей сутності у вигляді простого об'єкта.
|
|
7
|
+
// Також зберігає початковий стан для відстеження змін (dirty checking).
|
|
8
|
+
const snapshot = entity.snapshot();
|
|
9
|
+
|
|
10
|
+
entity.isDirty(); // Повертає true, якщо сутність була змінена після створення знімка.
|
|
11
|
+
entity.snapshotDiff(); // Повертає об'єкт з відмінностями між поточним станом сутності та знімком. Або null, якщо змін немає.
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Events
|
|
15
|
+
|
|
16
|
+
### EventBus
|
|
17
|
+
|
|
18
|
+
Сутність для керування подіями в системі.
|
|
19
|
+
В монолітних застосунках зазвичай використовується один глобальний EventBus і його можна описати за допомогою InMemoryEventBus, не потрібно залучати чергу, бо всі обробники працюють в одному процесі.
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
// Спершу треба описати події, які є в домені. Для цього є клас DomainEvent, який треба наслідувати.
|
|
23
|
+
class UserCreatedEvent extends DomainEvent<{ userId: string; username: string }> {
|
|
24
|
+
readonly eventName = 'UserCreatedEvent'; // без цього не буде працювати статична типізація
|
|
25
|
+
}
|
|
26
|
+
class UserUpdatedEvent extends DomainEvent<{ userId: string; username: string }> {
|
|
27
|
+
readonly eventName = 'UserUpdatedEvent';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Далі створюємо підписника на події, реалізуючи інтерфейс DomainEventSubscriber
|
|
31
|
+
class NewUserSubscriber implements DomainEventSubscriber {
|
|
32
|
+
// Підписуємося на події, які нас цікавлять
|
|
33
|
+
subscribedTo() {
|
|
34
|
+
return [UserCreatedEvent, UserUpdatedEvent];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Реалізуємо логіку обробки подій
|
|
38
|
+
async on(domainEvent: UserCreatedEvent|UserUpdatedEvent) {
|
|
39
|
+
console.log('Event received:', domainEvent);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Далі створюємо EventBus і реєструємо підписника
|
|
44
|
+
const eventBus = new InMemoryEventBus();
|
|
45
|
+
const subscriber = new NewUserSubscriber();
|
|
46
|
+
eventBus.addSubscribers([subscriber]);
|
|
47
|
+
const event = new UserCreatedEvent({ userId: '1', username: 'john_doe' });
|
|
48
|
+
const event2 = new UserUpdatedEvent({ userId: '1', username: 'john_doe_updated' });
|
|
49
|
+
// Публікуємо події
|
|
50
|
+
await eventBus.publish([event, event2]);
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
У поєднанні snapshot та EventBus можна реалізувати відстеження змін в сутностях і публікації відповідних подій.
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
class UserId extends EntityId<string> {}
|
|
57
|
+
|
|
58
|
+
interface IUser {
|
|
59
|
+
id: UserId;
|
|
60
|
+
username: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
class User extends Entity<IUser, string, UserId> {
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
class UserCreatedEvent extends DomainEvent<Partial<IUser>> {
|
|
67
|
+
readonly eventName = 'UserCreatedEvent'; // без цього не буде працювати статична типізація
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
class ChangeLoggerSubscriber implements DomainEventSubscriber {
|
|
71
|
+
// Підписуємося на події, які нас цікавлять
|
|
72
|
+
subscribedTo() {
|
|
73
|
+
return [UserCreatedEvent];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Реалізуємо логіку обробки подій
|
|
77
|
+
async on(domainEvent: UserCreatedEvent) {
|
|
78
|
+
console.log('Username changed:', domainEvent.username);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Далі створюємо EventBus і реєструємо підписника
|
|
83
|
+
const eventBus = new InMemoryEventBus();
|
|
84
|
+
const subscriber = new NewUserSubscriber();
|
|
85
|
+
eventBus.addSubscribers([subscriber]);
|
|
86
|
+
|
|
87
|
+
const user = User.create({ id: new UserId('1'), username: 'john_doe' });
|
|
88
|
+
// Створюємо знімок початкового стану
|
|
89
|
+
user.snapshot();
|
|
90
|
+
// Змінюємо ім'я користувача
|
|
91
|
+
user.username = 'john_doe_updated';
|
|
92
|
+
// Перевіряємо, чи є зміни
|
|
93
|
+
if (user.isDirty()) {
|
|
94
|
+
const diff = user.snapshotDiff();
|
|
95
|
+
if (diff) {
|
|
96
|
+
// записуємо подію в чергу (не publish). Бо якщо в циклі може бути багато подій і їх треба назбирати, а потім виконати, тому збираємо через push
|
|
97
|
+
eventBus.push(new UserCreatedEvent(diff));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Публікуємо всі події в черзі
|
|
101
|
+
await eventBus.publish();
|
|
102
|
+
```
|
package/build/AggregateRoot.d.ts
CHANGED
|
@@ -1,9 +1,4 @@
|
|
|
1
1
|
import { Entity } from './Entity';
|
|
2
2
|
import { EntityId } from './EntityId';
|
|
3
|
-
import { DomainEvent } from "./DomainEvent";
|
|
4
3
|
export declare abstract class AggregateRoot<T, E, H extends EntityId<E>> extends Entity<T, E, H> {
|
|
5
|
-
private _domainEvents;
|
|
6
|
-
pullDomainEvents(): DomainEvent<E>[];
|
|
7
|
-
pushDomainEvent(event: DomainEvent<E>): void;
|
|
8
4
|
}
|
|
9
|
-
export type AnyAggregateRoot<T> = AggregateRoot<any, T, any>;
|
package/build/AggregateRoot.js
CHANGED
|
@@ -6,18 +6,8 @@ var Entity_1 = require("./Entity");
|
|
|
6
6
|
var AggregateRoot = /** @class */ (function (_super) {
|
|
7
7
|
tslib_1.__extends(AggregateRoot, _super);
|
|
8
8
|
function AggregateRoot() {
|
|
9
|
-
|
|
10
|
-
_this._domainEvents = [];
|
|
11
|
-
return _this;
|
|
9
|
+
return _super !== null && _super.apply(this, arguments) || this;
|
|
12
10
|
}
|
|
13
|
-
AggregateRoot.prototype.pullDomainEvents = function () {
|
|
14
|
-
var domainEvents = this._domainEvents.slice();
|
|
15
|
-
this._domainEvents = [];
|
|
16
|
-
return domainEvents;
|
|
17
|
-
};
|
|
18
|
-
AggregateRoot.prototype.pushDomainEvent = function (event) {
|
|
19
|
-
this._domainEvents.push(event);
|
|
20
|
-
};
|
|
21
11
|
return AggregateRoot;
|
|
22
12
|
}(Entity_1.Entity));
|
|
23
13
|
exports.AggregateRoot = AggregateRoot;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AggregateRoot.js","sourceRoot":"","sources":["../src/AggregateRoot.ts"],"names":[],"mappings":";;;;AAAA,mCAAkC;
|
|
1
|
+
{"version":3,"file":"AggregateRoot.js","sourceRoot":"","sources":["../src/AggregateRoot.ts"],"names":[],"mappings":";;;;AAAA,mCAAkC;AAGlC;IAAyE,yCAAe;IAAxF;;IACA,CAAC;IAAD,oBAAC;AAAD,CAAC,AADD,CAAyE,eAAM,GAC9E;AADqB,sCAAa"}
|
package/build/DomainEvent.d.ts
CHANGED
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
import { EntityId } from "./EntityId";
|
|
2
|
-
import { AnyAggregateRoot } from "./AggregateRoot";
|
|
3
2
|
export declare abstract class DomainEvent<H> {
|
|
4
3
|
static EVENT_NAME: string;
|
|
5
|
-
readonly
|
|
6
|
-
readonly
|
|
7
|
-
readonly eventId: EntityId<H>;
|
|
4
|
+
readonly props: H;
|
|
5
|
+
readonly eventId: EntityId<never>;
|
|
8
6
|
readonly occurredOn: Date;
|
|
9
7
|
readonly eventName: string;
|
|
10
|
-
constructor(
|
|
11
|
-
|
|
8
|
+
constructor(props: H, eventId?: EntityId<never>, occurredOn?: Date);
|
|
9
|
+
toPrimitives(): H;
|
|
12
10
|
}
|
|
13
11
|
export type AnyDomainEvent = DomainEvent<any>;
|
|
14
12
|
export type DomainEventClass = typeof DomainEvent<any>;
|
|
15
|
-
type DomainEventAttributes = any;
|
|
16
|
-
export {};
|
package/build/DomainEvent.js
CHANGED
|
@@ -3,14 +3,18 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.DomainEvent = void 0;
|
|
4
4
|
var EntityId_1 = require("./EntityId");
|
|
5
5
|
var DomainEvent = /** @class */ (function () {
|
|
6
|
-
function DomainEvent(
|
|
6
|
+
function DomainEvent(props, eventId, occurredOn) {
|
|
7
|
+
var _a;
|
|
8
|
+
this.props = props;
|
|
7
9
|
var event = this.constructor;
|
|
8
|
-
this.eventName = event.EVENT_NAME;
|
|
10
|
+
this.eventName = (_a = event.EVENT_NAME) !== null && _a !== void 0 ? _a : this.constructor.name;
|
|
9
11
|
this.eventId = eventId || new EntityId_1.EntityId();
|
|
10
|
-
this.aggregate = aggregate;
|
|
11
|
-
this.aggregateId = aggregate.id;
|
|
12
12
|
this.occurredOn = occurredOn || new Date();
|
|
13
13
|
}
|
|
14
|
+
DomainEvent.prototype.toPrimitives = function () {
|
|
15
|
+
return this.props;
|
|
16
|
+
};
|
|
17
|
+
;
|
|
14
18
|
return DomainEvent;
|
|
15
19
|
}());
|
|
16
20
|
exports.DomainEvent = DomainEvent;
|
package/build/DomainEvent.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DomainEvent.js","sourceRoot":"","sources":["../src/DomainEvent.ts"],"names":[],"mappings":";;;AAAA,uCAAoC;
|
|
1
|
+
{"version":3,"file":"DomainEvent.js","sourceRoot":"","sources":["../src/DomainEvent.ts"],"names":[],"mappings":";;;AAAA,uCAAoC;AAEpC;IAQE,qBAAY,KAAQ,EAAE,OAAyB,EAAE,UAAiB;;QAChE,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QAEnB,IAAM,KAAK,GAAuB,IAAI,CAAC,WAAW,CAAC;QACnD,IAAI,CAAC,SAAS,GAAG,MAAA,KAAK,CAAC,UAAU,mCAAI,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC;QAC3D,IAAI,CAAC,OAAO,GAAG,OAAO,IAAI,IAAI,mBAAQ,EAAS,CAAC;QAChD,IAAI,CAAC,UAAU,GAAG,UAAU,IAAI,IAAI,IAAI,EAAE,CAAC;IAC7C,CAAC;IAED,kCAAY,GAAZ;QACE,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAAA,CAAC;IACJ,kBAAC;AAAD,CAAC,AApBD,IAoBC;AApBqB,kCAAW"}
|
package/build/Entity.d.ts
CHANGED
|
@@ -2,6 +2,10 @@ import { EntityId } from './EntityId';
|
|
|
2
2
|
export declare abstract class Entity<T, E, H extends EntityId<E>> {
|
|
3
3
|
protected readonly _id: H;
|
|
4
4
|
protected props: T;
|
|
5
|
+
protected _snapshot: string;
|
|
6
|
+
snapshot(): T;
|
|
7
|
+
snapshotDiff(): Partial<T> | null;
|
|
8
|
+
isDirty(): boolean;
|
|
5
9
|
protected constructor(props: T, id?: H | E);
|
|
6
10
|
equals(object?: Entity<T, E, H>): boolean;
|
|
7
11
|
get id(): H;
|
package/build/Entity.js
CHANGED
|
@@ -15,6 +15,31 @@ var Entity = /** @class */ (function () {
|
|
|
15
15
|
}
|
|
16
16
|
this.props = props;
|
|
17
17
|
}
|
|
18
|
+
Entity.prototype.snapshot = function () {
|
|
19
|
+
this._snapshot = JSON.stringify(this.props);
|
|
20
|
+
var copy = JSON.parse(this._snapshot);
|
|
21
|
+
Object.freeze(copy);
|
|
22
|
+
return copy;
|
|
23
|
+
};
|
|
24
|
+
Entity.prototype.snapshotDiff = function () {
|
|
25
|
+
if (!this._snapshot) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
var diffs = {};
|
|
29
|
+
var copy = JSON.parse(this._snapshot);
|
|
30
|
+
for (var key in this.props) {
|
|
31
|
+
if (JSON.stringify(this.props[key]) !== JSON.stringify(copy[key])) {
|
|
32
|
+
diffs[key] = this.props[key];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return Object.keys(diffs).length > 0 ? diffs : null;
|
|
36
|
+
};
|
|
37
|
+
Entity.prototype.isDirty = function () {
|
|
38
|
+
if (!this._snapshot) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
return this._snapshot !== JSON.stringify(this.props);
|
|
42
|
+
};
|
|
18
43
|
Entity.prototype.equals = function (object) {
|
|
19
44
|
if (object === null || object === undefined) {
|
|
20
45
|
return false;
|
package/build/Entity.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Entity.js","sourceRoot":"","sources":["../src/Entity.ts"],"names":[],"mappings":";;;AAAA,uCAAsC;AAEtC,IAAM,QAAQ,GAAG,UAA8B,CAAkB;IAC/D,OAAO,CAAC,YAAY,MAAM,CAAA;AAC5B,CAAC,CAAA;AAED;
|
|
1
|
+
{"version":3,"file":"Entity.js","sourceRoot":"","sources":["../src/Entity.ts"],"names":[],"mappings":";;;AAAA,uCAAsC;AAEtC,IAAM,QAAQ,GAAG,UAA8B,CAAkB;IAC/D,OAAO,CAAC,YAAY,MAAM,CAAA;AAC5B,CAAC,CAAA;AAED;IAiCE,gBAAsB,KAAQ,EAAE,EAAQ;QACtC,IAAI,EAAE,EAAE,CAAC;YACP,IAAI,CAAC,GAAG,GAAM,CAAC,OAAO,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,mBAAQ,CAAI,EAAE,CAAC,CAAC,CAAC;QACpE,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,GAAG,GAAM,CAAC,IAAI,mBAAQ,EAAE,CAAC,CAAC;QACjC,CAAC;QACD,IAAI,CAAC,KAAK,GAAG,KAAK,CAAA;IACpB,CAAC;IAnCD,yBAAQ,GAAR;QACE,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC5C,IAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAM,CAAC;QAC7C,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACpB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,6BAAY,GAAZ;QACE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YACpB,OAAO,IAAI,CAAC;QACd,CAAC;QACD,IAAM,KAAK,GAAe,EAAE,CAAC;QAC7B,IAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACxC,KAAK,IAAM,GAAG,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAC7B,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;gBAClE,KAAK,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YAC/B,CAAC;QACH,CAAC;QACD,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;IACtD,CAAC;IAED,wBAAO,GAAP;QACE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YACpB,OAAO,KAAK,CAAC;QACf,CAAC;QACD,OAAO,IAAI,CAAC,SAAS,KAAK,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACvD,CAAC;IAWM,uBAAM,GAAb,UAAc,MAAwB;QACpC,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YAC5C,OAAO,KAAK,CAAA;QACd,CAAC;QAED,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;YACpB,OAAO,IAAI,CAAA;QACb,CAAC;QAED,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YACtB,OAAO,KAAK,CAAA;QACd,CAAC;QAED,OAAO,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACrC,CAAC;IAED,sBAAI,sBAAE;aAAN;YACE,OAAO,IAAI,CAAC,GAAG,CAAA;QACjB,CAAC;;;OAAA;IAED,4BAAW,GAAX;QACE,OAAO,IAAI,CAAC,KAAK,CAAA;IACnB,CAAC;IAED,uBAAM,GAAN;QACE,OAAO,IAAI,CAAC,WAAW,EAAE,CAAA;IAC3B,CAAC;IACH,aAAC;AAAD,CAAC,AArED,IAqEC;AArEqB,wBAAM"}
|
package/build/Entity.spec.js
CHANGED
|
@@ -11,23 +11,30 @@ var TestEntity = /** @class */ (function (_super) {
|
|
|
11
11
|
TestEntity.create = function (props, id) {
|
|
12
12
|
return new TestEntity(props, id);
|
|
13
13
|
};
|
|
14
|
+
Object.defineProperty(TestEntity.prototype, "name", {
|
|
15
|
+
set: function (name) {
|
|
16
|
+
this.props.name = name;
|
|
17
|
+
},
|
|
18
|
+
enumerable: false,
|
|
19
|
+
configurable: true
|
|
20
|
+
});
|
|
14
21
|
return TestEntity;
|
|
15
22
|
}(Entity_1.Entity));
|
|
16
23
|
describe('Entity', function () {
|
|
17
24
|
var entity;
|
|
18
25
|
beforeAll(function () {
|
|
19
|
-
entity = TestEntity.create({ name: 'test' }, 1);
|
|
26
|
+
entity = TestEntity.create({ name: 'test' }, '1');
|
|
20
27
|
});
|
|
21
28
|
test('base', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
|
|
22
29
|
var entityNew, entity2, entity3;
|
|
23
30
|
return tslib_1.__generator(this, function (_a) {
|
|
24
31
|
entityNew = TestEntity.create({ name: 'test2' });
|
|
25
32
|
expect(entityNew.id.toString().length).toEqual(36); // random id
|
|
26
|
-
entity2 = TestEntity.create({ name: 'test2' }, 1);
|
|
33
|
+
entity2 = TestEntity.create({ name: 'test2' }, '1');
|
|
27
34
|
expect(entity.equals(entity2)).toBeTruthy(); // same id
|
|
28
35
|
expect(entity.equals(entity)).toBeTruthy(); // same instance
|
|
29
36
|
expect(entity.equals(null)).toBeFalsy(); // null
|
|
30
|
-
expect(entity.equals({ id: new EntityId_1.EntityId(1) })).toBeFalsy(); // not Entity
|
|
37
|
+
expect(entity.equals({ id: new EntityId_1.EntityId('1') })).toBeFalsy(); // not Entity
|
|
31
38
|
expect(entity.id.toPrimitive()).toEqual('1');
|
|
32
39
|
expect(entity.id.toString()).toEqual('6061cf31-edcb-5d22-9adc-927af7c186fa');
|
|
33
40
|
entity3 = TestEntity.create({ name: 'test2' }, entity.id);
|
|
@@ -37,5 +44,21 @@ describe('Entity', function () {
|
|
|
37
44
|
return [2 /*return*/];
|
|
38
45
|
});
|
|
39
46
|
}); });
|
|
47
|
+
test('snapshot', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
|
|
48
|
+
var snapshot;
|
|
49
|
+
return tslib_1.__generator(this, function (_a) {
|
|
50
|
+
snapshot = entity.snapshot();
|
|
51
|
+
expect(snapshot).toEqual({ name: 'test' });
|
|
52
|
+
expect(function () {
|
|
53
|
+
snapshot.name = 'changed';
|
|
54
|
+
}).toThrow();
|
|
55
|
+
expect(entity.isDirty()).toEqual(false);
|
|
56
|
+
expect(entity.snapshotDiff()).toEqual(null);
|
|
57
|
+
entity.name = 'test changed';
|
|
58
|
+
expect(entity.isDirty()).toEqual(true);
|
|
59
|
+
expect(entity.snapshotDiff()).toEqual({ "name": "test changed" });
|
|
60
|
+
return [2 /*return*/];
|
|
61
|
+
});
|
|
62
|
+
}); });
|
|
40
63
|
});
|
|
41
64
|
//# sourceMappingURL=Entity.spec.js.map
|
package/build/Entity.spec.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Entity.spec.js","sourceRoot":"","sources":["../src/Entity.spec.ts"],"names":[],"mappings":";;;AAAA,mCAAkC;AAClC,uCAAsC;AAKtC;IAAyB,sCAA2C;IAApE;;
|
|
1
|
+
{"version":3,"file":"Entity.spec.js","sourceRoot":"","sources":["../src/Entity.spec.ts"],"names":[],"mappings":"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"}
|
package/build/EntityId.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
export declare class EntityId<T> {
|
|
2
2
|
protected _recordId: T | null;
|
|
3
3
|
protected _uuid: string;
|
|
4
|
-
constructor(recordId?: T, isHash?: boolean, uuid?: string);
|
|
4
|
+
constructor(recordId?: T | string, isHash?: boolean, uuid?: string);
|
|
5
5
|
private createUUID;
|
|
6
6
|
get hashOptions(): [string, number];
|
|
7
7
|
toHash(): string;
|
|
8
|
-
fromHash(hash: string): EntityId<T>;
|
|
8
|
+
protected fromHash(hash: string): EntityId<T>;
|
|
9
9
|
toPrimitive(): T;
|
|
10
10
|
toString(): string;
|
|
11
11
|
equals(id: EntityId<T>): boolean;
|
package/build/EntityId.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EntityId.js","sourceRoot":"","sources":["../src/EntityId.ts"],"names":[],"mappings":";;;;AAAA,0EAAuC;AACvC,8CAA+C;AAC/C,4DAA8B;AAC9B,+CAA4C;AAE5C;IAIE,kBAAY,
|
|
1
|
+
{"version":3,"file":"EntityId.js","sourceRoot":"","sources":["../src/EntityId.ts"],"names":[],"mappings":";;;;AAAA,0EAAuC;AACvC,8CAA+C;AAC/C,4DAA8B;AAC9B,+CAA4C;AAE5C;IAIE,kBAAY,QAAyB,EAAE,MAAsB,EAAE,IAAmB;QAAtE,yBAAA,EAAA,eAAyB;QAAE,uBAAA,EAAA,cAAsB;QAAE,qBAAA,EAAA,WAAmB;QAHxE,cAAS,GAAW,IAAI,CAAC;QACzB,UAAK,GAAW,IAAI,CAAC;QAG7B,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC,CAAC,CAAC,QAAa,CAAC,CAAC,CAAC,IAAI,CAAC;QACjD,IAAI,MAAM,EAAE,CAAC;YACX,IAAI,CAAC,QAAQ,CAAC,QAAkB,CAAC,CAAC;QACpC,CAAC;QACD,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,IAAI,CAAC,UAAU,EAAE,CAAC;QACpB,CAAC;IACH,CAAC;IAEO,6BAAU,GAAlB;QACE,IAAI,CAAC,KAAK,GAAG,IAAA,wBAAU,EAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,IAAA,gBAAQ,GAAE,EAAE,IAAA,wBAAU,EAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC;IACtH,CAAC;IAED,sBAAI,iCAAW;aAAf;YACE,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QACpC,CAAC;;;OAAA;IAED,yBAAM,GAAN;QACE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;QACnD,CAAC;QACD,IAAM,OAAO,QAAO,iBAAO,YAAP,iBAAO,kCAAI,IAAI,CAAC,WAAW,YAAC,CAAC;QACjD,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC;IACvD,CAAC;IAES,2BAAQ,GAAlB,UAAmB,IAAY;QAC7B,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,OAAO,IAAI,CAAC;QACd,CAAC;QACD,IAAM,OAAO,QAAO,iBAAO,YAAP,iBAAO,kCAAI,IAAI,CAAC,WAAW,YAAC,CAAC;QACjD,IAAM,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QACpC,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,MAAM,IAAI,qBAAS,CAAC,uBAAgB,IAAI,CAAE,CAAC,CAAC;QAC9C,CAAC;QACD,IAAI,CAAC,SAAS,GAAM,CAAC,OAAO,IAAI,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QAChF,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,8BAAW,GAAX;QACE,OAAO,IAAI,CAAC,SAAS,CAAC;IACxB,CAAC;IAED,2BAAQ,GAAR;QACE,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAED,yBAAM,GAAN,UAAO,EAAe;QACpB,OAAO,IAAI,CAAC,KAAK,KAAK,EAAE,CAAC,KAAK,CAAC;IACjC,CAAC;IACH,eAAC;AAAD,CAAC,AAvDD,IAuDC;AAvDY,4BAAQ"}
|
package/build/EntityId.spec.js
CHANGED
|
@@ -49,16 +49,17 @@ describe('ValueObject', function () {
|
|
|
49
49
|
});
|
|
50
50
|
}); });
|
|
51
51
|
test('toHash', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
|
|
52
|
-
var id, id2, id3;
|
|
52
|
+
var id, idFromHash, id2, id3;
|
|
53
53
|
return tslib_1.__generator(this, function (_a) {
|
|
54
54
|
id = new Test2Id('123');
|
|
55
55
|
expect(id.toHash()).toEqual('9KdMR');
|
|
56
|
-
|
|
57
|
-
expect(
|
|
58
|
-
expect(
|
|
56
|
+
idFromHash = new Test2Id('VLoxm', true);
|
|
57
|
+
expect(idFromHash.toPrimitive()).toEqual('1');
|
|
58
|
+
expect(idFromHash.toPrimitive()).toEqual('1');
|
|
59
|
+
expect(idFromHash.toString()).toEqual('090bb5d1-267b-5967-84c8-95bc1bda380f');
|
|
59
60
|
id2 = new TestId(123);
|
|
60
61
|
expect(id2.toHash()).toEqual('BJejP');
|
|
61
|
-
expect(
|
|
62
|
+
expect(new TestId('BJejP', true).toPrimitive()).toEqual('123');
|
|
62
63
|
id3 = new Test2Id();
|
|
63
64
|
expect(function () { return id3.toHash(); }).toThrow('Cannot hash an empty recordId');
|
|
64
65
|
return [2 /*return*/];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EntityId.spec.js","sourceRoot":"","sources":["../src/EntityId.spec.ts"],"names":[],"mappings":";;;AAAA,uCAAsC;AACtC,+CAA4C;AAE5C;IAAqB,kCAAgB;IAArC;;IACA,CAAC;IAAD,aAAC;AAAD,CAAC,AADD,CAAqB,mBAAQ,GAC5B;AAED;IAAsB,mCAAgB;IAAtC;;IACA,CAAC;IAAD,cAAC;AAAD,CAAC,AADD,CAAsB,mBAAQ,GAC7B;AAED,QAAQ,CAAC,aAAa,EAAE;IACtB,IAAI,CAAC,MAAM,EAAE;;;YACL,EAAE,GAAG,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC;YACnB,GAAG,GAAG,IAAI,mBAAQ,CAAC,GAAG,CAAC,CAAC;YACxB,GAAG,GAAG,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC;YAC1B,MAAM,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YACxB,MAAM,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YAEtB,GAAG,GAAG,IAAI,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;YACvC,MAAM,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;;;SACzC,CAAC,CAAC;IACH,IAAI,CAAC,UAAU,EAAE;;YACf,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;YAErB,MAAM,CAAC,CAAC,IAAI,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YACnE,MAAM,CAAC,CAAC,IAAI,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YAE9D,IAAI,CAAC;gBACH,IAAI,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;YAC1B,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,sDAAsD;gBACtD,MAAM,CAAC,GAAG,CAAC,CAAC,cAAc,CAAC,qBAAS,CAAC,CAAC;gBACtC,sDAAsD;gBACtD,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAC;YAClD,CAAC;;;SACF,CAAC,CAAC;IACH,IAAI,CAAC,QAAQ,EAAE;;;YACP,EAAE,GAAG,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC;YAC9B,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"EntityId.spec.js","sourceRoot":"","sources":["../src/EntityId.spec.ts"],"names":[],"mappings":"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"}
|
package/build/EventBus.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { AnyDomainEvent } from './DomainEvent';
|
|
2
2
|
import { DomainEventSubscriber } from "./DomainEventSubscriber";
|
|
3
3
|
export interface EventBus {
|
|
4
|
+
push(event: AnyDomainEvent): Promise<void>;
|
|
5
|
+
clear(): Promise<void>;
|
|
4
6
|
publish(events: AnyDomainEvent[]): Promise<void>;
|
|
5
7
|
addSubscribers(subscribers: DomainEventSubscriber[]): void;
|
|
6
8
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
var tslib_1 = require("tslib");
|
|
4
|
+
var InMemoryEventBus_1 = require("./eventBus/InMemoryEventBus");
|
|
5
|
+
var DomainEvent_1 = require("./DomainEvent");
|
|
6
|
+
var EntityId_1 = require("./EntityId");
|
|
7
|
+
var Entity_1 = require("./Entity");
|
|
8
|
+
describe('EventBus', function () {
|
|
9
|
+
var NewUserCreated = /** @class */ (function (_super) {
|
|
10
|
+
tslib_1.__extends(NewUserCreated, _super);
|
|
11
|
+
function NewUserCreated() {
|
|
12
|
+
return _super !== null && _super.apply(this, arguments) || this;
|
|
13
|
+
}
|
|
14
|
+
return NewUserCreated;
|
|
15
|
+
}(DomainEvent_1.DomainEvent));
|
|
16
|
+
var NewUserCreated2 = /** @class */ (function (_super) {
|
|
17
|
+
tslib_1.__extends(NewUserCreated2, _super);
|
|
18
|
+
function NewUserCreated2() {
|
|
19
|
+
return _super !== null && _super.apply(this, arguments) || this;
|
|
20
|
+
}
|
|
21
|
+
NewUserCreated2.EVENT_NAME = 'Event2';
|
|
22
|
+
return NewUserCreated2;
|
|
23
|
+
}(DomainEvent_1.DomainEvent));
|
|
24
|
+
var NewUserSubscriber = /** @class */ (function () {
|
|
25
|
+
function NewUserSubscriber() {
|
|
26
|
+
}
|
|
27
|
+
NewUserSubscriber.prototype.subscribedTo = function () {
|
|
28
|
+
return [NewUserCreated, NewUserCreated2];
|
|
29
|
+
};
|
|
30
|
+
NewUserSubscriber.prototype.on = function (domainEvent) {
|
|
31
|
+
return tslib_1.__awaiter(this, void 0, void 0, function () {
|
|
32
|
+
return tslib_1.__generator(this, function (_a) {
|
|
33
|
+
console.log('Event received:', domainEvent);
|
|
34
|
+
return [2 /*return*/];
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
return NewUserSubscriber;
|
|
39
|
+
}());
|
|
40
|
+
test('lifecycle', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
|
|
41
|
+
var newUserSubscriber, hub, event, event2;
|
|
42
|
+
return tslib_1.__generator(this, function (_a) {
|
|
43
|
+
switch (_a.label) {
|
|
44
|
+
case 0:
|
|
45
|
+
newUserSubscriber = new NewUserSubscriber();
|
|
46
|
+
newUserSubscriber.on = jest.fn();
|
|
47
|
+
hub = new InMemoryEventBus_1.InMemoryEventBus();
|
|
48
|
+
hub.addSubscribers([newUserSubscriber]);
|
|
49
|
+
event = new NewUserCreated({ username: 'test' });
|
|
50
|
+
event2 = new NewUserCreated2({ username: 'test2' });
|
|
51
|
+
expect(event.eventName).toEqual('NewUserCreated');
|
|
52
|
+
expect(event2.eventName).toEqual('Event2');
|
|
53
|
+
return [4 /*yield*/, hub.publish([event, event2])];
|
|
54
|
+
case 1:
|
|
55
|
+
_a.sent();
|
|
56
|
+
expect(newUserSubscriber.on).toHaveBeenCalledTimes(2);
|
|
57
|
+
return [2 /*return*/];
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}); });
|
|
61
|
+
test('user example', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
|
|
62
|
+
var UserId, User, UserCreatedEvent, ChangeLoggerSubscriber, eventBus, subscriber, mockOn, user, event, diff;
|
|
63
|
+
return tslib_1.__generator(this, function (_a) {
|
|
64
|
+
switch (_a.label) {
|
|
65
|
+
case 0:
|
|
66
|
+
UserId = /** @class */ (function (_super) {
|
|
67
|
+
tslib_1.__extends(UserId, _super);
|
|
68
|
+
function UserId() {
|
|
69
|
+
return _super !== null && _super.apply(this, arguments) || this;
|
|
70
|
+
}
|
|
71
|
+
return UserId;
|
|
72
|
+
}(EntityId_1.EntityId));
|
|
73
|
+
User = /** @class */ (function (_super) {
|
|
74
|
+
tslib_1.__extends(User, _super);
|
|
75
|
+
function User() {
|
|
76
|
+
return _super !== null && _super.apply(this, arguments) || this;
|
|
77
|
+
}
|
|
78
|
+
User.create = function (props) {
|
|
79
|
+
return new User(props, props.id);
|
|
80
|
+
};
|
|
81
|
+
Object.defineProperty(User.prototype, "username", {
|
|
82
|
+
get: function () {
|
|
83
|
+
return this.props.username;
|
|
84
|
+
},
|
|
85
|
+
set: function (username) {
|
|
86
|
+
this.props.username = username;
|
|
87
|
+
},
|
|
88
|
+
enumerable: false,
|
|
89
|
+
configurable: true
|
|
90
|
+
});
|
|
91
|
+
return User;
|
|
92
|
+
}(Entity_1.Entity));
|
|
93
|
+
UserCreatedEvent = /** @class */ (function (_super) {
|
|
94
|
+
tslib_1.__extends(UserCreatedEvent, _super);
|
|
95
|
+
function UserCreatedEvent() {
|
|
96
|
+
var _this = _super !== null && _super.apply(this, arguments) || this;
|
|
97
|
+
_this.eventName = 'UserCreatedEvent'; // без цього не буде працювати статична типізація
|
|
98
|
+
return _this;
|
|
99
|
+
}
|
|
100
|
+
Object.defineProperty(UserCreatedEvent.prototype, "username", {
|
|
101
|
+
get: function () {
|
|
102
|
+
return this.props.username;
|
|
103
|
+
},
|
|
104
|
+
enumerable: false,
|
|
105
|
+
configurable: true
|
|
106
|
+
});
|
|
107
|
+
return UserCreatedEvent;
|
|
108
|
+
}(DomainEvent_1.DomainEvent));
|
|
109
|
+
ChangeLoggerSubscriber = /** @class */ (function () {
|
|
110
|
+
function ChangeLoggerSubscriber() {
|
|
111
|
+
}
|
|
112
|
+
// Підписуємося на події, які нас цікавлять
|
|
113
|
+
ChangeLoggerSubscriber.prototype.subscribedTo = function () {
|
|
114
|
+
return [UserCreatedEvent];
|
|
115
|
+
};
|
|
116
|
+
// Реалізуємо логіку обробки подій
|
|
117
|
+
ChangeLoggerSubscriber.prototype.on = function (domainEvent) {
|
|
118
|
+
return tslib_1.__awaiter(this, void 0, void 0, function () {
|
|
119
|
+
return tslib_1.__generator(this, function (_a) {
|
|
120
|
+
console.log('Username changed:', domainEvent.username);
|
|
121
|
+
return [2 /*return*/];
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
};
|
|
125
|
+
return ChangeLoggerSubscriber;
|
|
126
|
+
}());
|
|
127
|
+
eventBus = new InMemoryEventBus_1.InMemoryEventBus();
|
|
128
|
+
subscriber = new ChangeLoggerSubscriber();
|
|
129
|
+
mockOn = jest.spyOn(subscriber, 'on');
|
|
130
|
+
eventBus.addSubscribers([subscriber]);
|
|
131
|
+
user = User.create({ id: new UserId('1'), username: 'john_doe' });
|
|
132
|
+
// Створюємо знімок початкового стану
|
|
133
|
+
user.snapshot();
|
|
134
|
+
// Змінюємо ім'я користувача
|
|
135
|
+
user.username = 'john_doe_updated';
|
|
136
|
+
// Перевіряємо, чи є зміни
|
|
137
|
+
if (user.isDirty()) {
|
|
138
|
+
diff = user.snapshotDiff();
|
|
139
|
+
if (diff) {
|
|
140
|
+
event = new UserCreatedEvent(diff);
|
|
141
|
+
// записуємо подію в чергу (не publish). Бо якщо в циклі може бути багато подій і їх треба назбирати, а потім виконати, тому збираємо через push
|
|
142
|
+
eventBus.push(event);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
expect(mockOn).not.toHaveBeenCalled();
|
|
146
|
+
// Публікуємо всі події в черзі
|
|
147
|
+
return [4 /*yield*/, eventBus.publish()];
|
|
148
|
+
case 1:
|
|
149
|
+
// Публікуємо всі події в черзі
|
|
150
|
+
_a.sent();
|
|
151
|
+
expect(mockOn).toHaveBeenCalledWith(event);
|
|
152
|
+
return [2 /*return*/];
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
}); });
|
|
156
|
+
});
|
|
157
|
+
//# sourceMappingURL=EventBus.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EventBus.spec.js","sourceRoot":"","sources":["../src/EventBus.spec.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,6 +4,9 @@ import { AnyDomainEvent } from '../DomainEvent';
|
|
|
4
4
|
import { EventBus } from '../EventBus';
|
|
5
5
|
import { DomainEventSubscriber } from "../DomainEventSubscriber";
|
|
6
6
|
export declare class InMemoryEventBus extends EventEmitter implements EventBus {
|
|
7
|
-
|
|
7
|
+
protected _events: AnyDomainEvent[];
|
|
8
|
+
push(event: AnyDomainEvent): Promise<void>;
|
|
9
|
+
clear(): Promise<void>;
|
|
10
|
+
publish(events?: AnyDomainEvent[]): Promise<void>;
|
|
8
11
|
addSubscribers(subscribers: DomainEventSubscriber[]): void;
|
|
9
12
|
}
|
|
@@ -6,27 +6,54 @@ var events_1 = require("events");
|
|
|
6
6
|
var InMemoryEventBus = /** @class */ (function (_super) {
|
|
7
7
|
tslib_1.__extends(InMemoryEventBus, _super);
|
|
8
8
|
function InMemoryEventBus() {
|
|
9
|
-
|
|
9
|
+
var _this = _super !== null && _super.apply(this, arguments) || this;
|
|
10
|
+
_this._events = [];
|
|
11
|
+
return _this;
|
|
10
12
|
}
|
|
13
|
+
InMemoryEventBus.prototype.push = function (event) {
|
|
14
|
+
return tslib_1.__awaiter(this, void 0, void 0, function () {
|
|
15
|
+
return tslib_1.__generator(this, function (_a) {
|
|
16
|
+
this._events.push(event);
|
|
17
|
+
return [2 /*return*/];
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
InMemoryEventBus.prototype.clear = function () {
|
|
22
|
+
return tslib_1.__awaiter(this, void 0, void 0, function () {
|
|
23
|
+
return tslib_1.__generator(this, function (_a) {
|
|
24
|
+
this._events = [];
|
|
25
|
+
return [2 /*return*/];
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
};
|
|
11
29
|
InMemoryEventBus.prototype.publish = function (events) {
|
|
30
|
+
if (events === void 0) { events = []; }
|
|
12
31
|
return tslib_1.__awaiter(this, void 0, void 0, function () {
|
|
13
|
-
var _i,
|
|
32
|
+
var allEvents, _i, allEvents_1, event;
|
|
14
33
|
return tslib_1.__generator(this, function (_a) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
34
|
+
switch (_a.label) {
|
|
35
|
+
case 0:
|
|
36
|
+
allEvents = (this._events || []).concat(events !== null && events !== void 0 ? events : []);
|
|
37
|
+
for (_i = 0, allEvents_1 = allEvents; _i < allEvents_1.length; _i++) {
|
|
38
|
+
event = allEvents_1[_i];
|
|
39
|
+
this.emit(event.eventName, event);
|
|
40
|
+
}
|
|
41
|
+
return [4 /*yield*/, this.clear()];
|
|
42
|
+
case 1:
|
|
43
|
+
_a.sent();
|
|
44
|
+
return [2 /*return*/];
|
|
18
45
|
}
|
|
19
|
-
return [2 /*return*/];
|
|
20
46
|
});
|
|
21
47
|
});
|
|
22
48
|
};
|
|
23
49
|
InMemoryEventBus.prototype.addSubscribers = function (subscribers) {
|
|
50
|
+
var _a;
|
|
24
51
|
for (var _i = 0, subscribers_1 = subscribers; _i < subscribers_1.length; _i++) {
|
|
25
52
|
var subscriber = subscribers_1[_i];
|
|
26
53
|
var events = subscriber.subscribedTo();
|
|
27
|
-
for (var
|
|
28
|
-
var event =
|
|
29
|
-
this.on(event.EVENT_NAME, subscriber.on.bind(subscriber));
|
|
54
|
+
for (var _b = 0, events_2 = events; _b < events_2.length; _b++) {
|
|
55
|
+
var event = events_2[_b];
|
|
56
|
+
this.on((_a = event.EVENT_NAME) !== null && _a !== void 0 ? _a : event.name, subscriber.on.bind(subscriber));
|
|
30
57
|
}
|
|
31
58
|
}
|
|
32
59
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InMemoryEventBus.js","sourceRoot":"","sources":["../../src/eventBus/InMemoryEventBus.ts"],"names":[],"mappings":";;;;AAAA,iCAAsC;AAKtC;IAAsC,4CAAY;IAAlD;;
|
|
1
|
+
{"version":3,"file":"InMemoryEventBus.js","sourceRoot":"","sources":["../../src/eventBus/InMemoryEventBus.ts"],"names":[],"mappings":";;;;AAAA,iCAAsC;AAKtC;IAAsC,4CAAY;IAAlD;;QACY,aAAO,GAAqB,EAAE,CAAC;;IA0B3C,CAAC;IAxBO,+BAAI,GAAV,UAAW,KAAqB;;;gBAC9B,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;;;;KAC1B;IAEK,gCAAK,GAAX;;;gBACE,IAAI,CAAC,OAAO,GAAG,EAAE,CAAC;;;;KACnB;IAEK,kCAAO,GAAb,UAAc,MAA6B;QAA7B,uBAAA,EAAA,WAA6B;;;;;;wBACnC,SAAS,GAAG,CAAC,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,aAAN,MAAM,cAAN,MAAM,GAAI,EAAE,CAAC,CAAC;wBAC5D,WAA6B,EAAT,uBAAS,EAAT,uBAAS,EAAT,IAAS,EAAE,CAAC;4BAArB,KAAK;4BACd,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;wBACpC,CAAC;wBACD,qBAAM,IAAI,CAAC,KAAK,EAAE,EAAA;;wBAAlB,SAAkB,CAAC;;;;;KACpB;IAED,yCAAc,GAAd,UAAe,WAAoC;;QACjD,KAAyB,UAAW,EAAX,2BAAW,EAAX,yBAAW,EAAX,IAAW,EAAE,CAAC;YAAlC,IAAM,UAAU,oBAAA;YACnB,IAAM,MAAM,GAAG,UAAU,CAAC,YAAY,EAAE,CAAC;YACzC,KAAoB,UAAM,EAAN,iBAAM,EAAN,oBAAM,EAAN,IAAM,EAAE,CAAC;gBAAxB,IAAM,KAAK,eAAA;gBACd,IAAI,CAAC,EAAE,CAAC,MAAA,KAAK,CAAC,UAAU,mCAAI,KAAK,CAAC,IAAI,EAAE,UAAU,CAAC,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;YAC1E,CAAC;QACH,CAAC;IACH,CAAC;IACH,uBAAC;AAAD,CAAC,AA3BD,CAAsC,qBAAY,GA2BjD;AA3BY,4CAAgB"}
|
|
@@ -20,9 +20,6 @@ var TestEvent = /** @class */ (function (_super) {
|
|
|
20
20
|
function TestEvent() {
|
|
21
21
|
return _super !== null && _super.apply(this, arguments) || this;
|
|
22
22
|
}
|
|
23
|
-
TestEvent.prototype.toPrimitives = function () {
|
|
24
|
-
return {};
|
|
25
|
-
};
|
|
26
23
|
TestEvent.EVENT_NAME = 'TestEvent';
|
|
27
24
|
return TestEvent;
|
|
28
25
|
}(DomainEvent_1.DomainEvent));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InMemoryEventBus.spec.js","sourceRoot":"","sources":["../../src/eventBus/InMemoryEventBus.spec.ts"],"names":[],"mappings":";;;AAAA,uDAAoD;AACpD,8CAA6D;AAC7D,wCAAqC;AAErC,kDAA+C;AAI/C;IAA4B,yCAAuD;IAAnF;;IAIA,CAAC;IAHQ,oBAAM,GAAb,UAAc,KAAqB,EAAE,EAA2B;QAC9D,OAAO,IAAI,aAAa,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACtC,CAAC;IACH,oBAAC;AAAD,CAAC,AAJD,CAA4B,6BAAa,GAIxC;AAED;IAAwB,
|
|
1
|
+
{"version":3,"file":"InMemoryEventBus.spec.js","sourceRoot":"","sources":["../../src/eventBus/InMemoryEventBus.spec.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/AggregateRoot.ts
CHANGED
|
@@ -1,20 +1,5 @@
|
|
|
1
1
|
import { Entity } from './Entity';
|
|
2
2
|
import { EntityId } from './EntityId';
|
|
3
|
-
import {DomainEvent} from "./DomainEvent";
|
|
4
3
|
|
|
5
4
|
export abstract class AggregateRoot<T, E, H extends EntityId<E>> extends Entity<T, E, H> {
|
|
6
|
-
private _domainEvents: DomainEvent<E>[] = [];
|
|
7
|
-
|
|
8
|
-
pullDomainEvents(): DomainEvent<E>[] {
|
|
9
|
-
const domainEvents = this._domainEvents.slice();
|
|
10
|
-
this._domainEvents = [];
|
|
11
|
-
return domainEvents;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
pushDomainEvent(event: DomainEvent<E>): void {
|
|
15
|
-
this._domainEvents.push(event);
|
|
16
|
-
}
|
|
17
5
|
}
|
|
18
|
-
|
|
19
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
20
|
-
export type AnyAggregateRoot<T> = AggregateRoot<any, T, any>;
|
package/src/DomainEvent.ts
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
import {EntityId} from "./EntityId";
|
|
2
|
-
import {AnyAggregateRoot} from "./AggregateRoot";
|
|
3
2
|
|
|
4
3
|
export abstract class DomainEvent<H> {
|
|
5
4
|
static EVENT_NAME: string;
|
|
6
5
|
|
|
7
|
-
readonly
|
|
8
|
-
readonly
|
|
9
|
-
readonly eventId: EntityId<H>;
|
|
6
|
+
readonly props: H;
|
|
7
|
+
readonly eventId: EntityId<never>;
|
|
10
8
|
readonly occurredOn: Date;
|
|
11
9
|
readonly eventName: string;
|
|
12
10
|
|
|
13
|
-
constructor(
|
|
11
|
+
constructor(props: H, eventId?: EntityId<never>, occurredOn?: Date) {
|
|
12
|
+
this.props = props;
|
|
13
|
+
|
|
14
14
|
const event = <typeof DomainEvent>this.constructor;
|
|
15
|
-
this.eventName = event.EVENT_NAME;
|
|
16
|
-
this.eventId = eventId || new EntityId<
|
|
17
|
-
this.aggregate = aggregate;
|
|
18
|
-
this.aggregateId = aggregate.id;
|
|
15
|
+
this.eventName = event.EVENT_NAME ?? this.constructor.name;
|
|
16
|
+
this.eventId = eventId || new EntityId<never>();
|
|
19
17
|
this.occurredOn = occurredOn || new Date();
|
|
20
18
|
}
|
|
21
19
|
|
|
22
|
-
|
|
20
|
+
toPrimitives(): H {
|
|
21
|
+
return this.props;
|
|
22
|
+
};
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -27,6 +27,3 @@ export type AnyDomainEvent = DomainEvent<any>;
|
|
|
27
27
|
|
|
28
28
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
29
29
|
export type DomainEventClass = typeof DomainEvent<any>;
|
|
30
|
-
|
|
31
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
32
|
-
type DomainEventAttributes = any;
|
package/src/Entity.spec.ts
CHANGED
|
@@ -4,28 +4,32 @@ import { EntityId } from './EntityId';
|
|
|
4
4
|
interface TestProps {
|
|
5
5
|
name: string;
|
|
6
6
|
}
|
|
7
|
-
class TestEntity extends Entity<TestProps,
|
|
8
|
-
static create(props: TestProps, id?:EntityId<
|
|
7
|
+
class TestEntity extends Entity<TestProps, string, EntityId<string>> {
|
|
8
|
+
static create(props: TestProps, id?:EntityId<string>|string): TestEntity {
|
|
9
9
|
return new TestEntity(props, id);
|
|
10
10
|
}
|
|
11
|
+
|
|
12
|
+
set name(name: string) {
|
|
13
|
+
this.props.name = name;
|
|
14
|
+
}
|
|
11
15
|
}
|
|
12
16
|
|
|
13
17
|
describe('Entity', () => {
|
|
14
18
|
let entity: TestEntity;
|
|
15
19
|
|
|
16
20
|
beforeAll(() => {
|
|
17
|
-
entity = TestEntity.create({ name: 'test' }, 1);
|
|
21
|
+
entity = TestEntity.create({ name: 'test' }, '1');
|
|
18
22
|
});
|
|
19
23
|
|
|
20
24
|
test('base', async () => {
|
|
21
25
|
const entityNew = TestEntity.create({ name: 'test2' });
|
|
22
26
|
expect(entityNew.id.toString().length).toEqual(36); // random id
|
|
23
27
|
|
|
24
|
-
const entity2 = TestEntity.create({ name: 'test2' }, 1);
|
|
28
|
+
const entity2 = TestEntity.create({ name: 'test2' }, '1');
|
|
25
29
|
expect(entity.equals(entity2)).toBeTruthy(); // same id
|
|
26
30
|
expect(entity.equals(entity)).toBeTruthy(); // same instance
|
|
27
31
|
expect(entity.equals(null)).toBeFalsy(); // null
|
|
28
|
-
expect(entity.equals(<TestEntity>{ id: new EntityId(1) })).toBeFalsy(); // not Entity
|
|
32
|
+
expect(entity.equals(<TestEntity>{ id: new EntityId('1') })).toBeFalsy(); // not Entity
|
|
29
33
|
expect(entity.id.toPrimitive()).toEqual('1');
|
|
30
34
|
expect(entity.id.toString()).toEqual('6061cf31-edcb-5d22-9adc-927af7c186fa');
|
|
31
35
|
|
|
@@ -34,4 +38,18 @@ describe('Entity', () => {
|
|
|
34
38
|
expect(entity3.id.toPrimitive()).toEqual('1');
|
|
35
39
|
expect(entity3.id.toString()).toEqual('6061cf31-edcb-5d22-9adc-927af7c186fa');
|
|
36
40
|
});
|
|
41
|
+
|
|
42
|
+
test('snapshot', async () => {
|
|
43
|
+
const snapshot = entity.snapshot();
|
|
44
|
+
expect(snapshot).toEqual({ name: 'test' });
|
|
45
|
+
|
|
46
|
+
expect(() => {
|
|
47
|
+
snapshot.name = 'changed';
|
|
48
|
+
}).toThrow()
|
|
49
|
+
expect(entity.isDirty()).toEqual(false);
|
|
50
|
+
expect(entity.snapshotDiff()).toEqual(null);
|
|
51
|
+
entity.name = 'test changed';
|
|
52
|
+
expect(entity.isDirty()).toEqual(true);
|
|
53
|
+
expect(entity.snapshotDiff()).toEqual({"name": "test changed"});
|
|
54
|
+
});
|
|
37
55
|
});
|
package/src/Entity.ts
CHANGED
|
@@ -7,6 +7,35 @@ const isEntity = <T, E, M extends EntityId<E>>(v: Entity<T, E, M>): v is Entity<
|
|
|
7
7
|
export abstract class Entity<T, E, H extends EntityId<E>> {
|
|
8
8
|
protected readonly _id: H;
|
|
9
9
|
protected props: T;
|
|
10
|
+
protected _snapshot: string;
|
|
11
|
+
|
|
12
|
+
snapshot(): T {
|
|
13
|
+
this._snapshot = JSON.stringify(this.props);
|
|
14
|
+
const copy = JSON.parse(this._snapshot) as T;
|
|
15
|
+
Object.freeze(copy);
|
|
16
|
+
return copy;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
snapshotDiff(): Partial<T> | null {
|
|
20
|
+
if (!this._snapshot) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
const diffs: Partial<T> = {};
|
|
24
|
+
const copy = JSON.parse(this._snapshot);
|
|
25
|
+
for (const key in this.props) {
|
|
26
|
+
if (JSON.stringify(this.props[key]) !== JSON.stringify(copy[key])) {
|
|
27
|
+
diffs[key] = this.props[key];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return Object.keys(diffs).length > 0 ? diffs : null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
isDirty(): boolean {
|
|
34
|
+
if (!this._snapshot) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
return this._snapshot !== JSON.stringify(this.props);
|
|
38
|
+
}
|
|
10
39
|
|
|
11
40
|
protected constructor(props: T, id?: H|E) {
|
|
12
41
|
if (id) {
|
package/src/EntityId.spec.ts
CHANGED
|
@@ -36,13 +36,14 @@ describe('ValueObject', () => {
|
|
|
36
36
|
test('toHash', async () => {
|
|
37
37
|
const id = new Test2Id('123');
|
|
38
38
|
expect(id.toHash()).toEqual('9KdMR');
|
|
39
|
-
|
|
40
|
-
expect(
|
|
41
|
-
expect(
|
|
39
|
+
const idFromHash = new Test2Id('VLoxm', true);
|
|
40
|
+
expect(idFromHash.toPrimitive()).toEqual('1');
|
|
41
|
+
expect(idFromHash.toPrimitive()).toEqual('1');
|
|
42
|
+
expect(idFromHash.toString()).toEqual('090bb5d1-267b-5967-84c8-95bc1bda380f');
|
|
42
43
|
|
|
43
44
|
const id2 = new TestId(123);
|
|
44
45
|
expect(id2.toHash()).toEqual('BJejP');
|
|
45
|
-
expect(
|
|
46
|
+
expect(new TestId('BJejP', true).toPrimitive()).toEqual('123');
|
|
46
47
|
|
|
47
48
|
const id3 = new Test2Id();
|
|
48
49
|
expect(() => id3.toHash()).toThrow('Cannot hash an empty recordId');
|
package/src/EntityId.ts
CHANGED
|
@@ -7,8 +7,8 @@ export class EntityId<T> {
|
|
|
7
7
|
protected _recordId: T|null = null;
|
|
8
8
|
protected _uuid: string = null;
|
|
9
9
|
|
|
10
|
-
constructor(recordId: T = null, isHash:boolean = false, uuid: string = null) {
|
|
11
|
-
this._recordId = recordId ? recordId : null;
|
|
10
|
+
constructor(recordId: T|string = null, isHash:boolean = false, uuid: string = null) {
|
|
11
|
+
this._recordId = recordId ? recordId as T : null;
|
|
12
12
|
if (isHash) {
|
|
13
13
|
this.fromHash(recordId as string);
|
|
14
14
|
}
|
|
@@ -33,7 +33,7 @@ export class EntityId<T> {
|
|
|
33
33
|
return hashids.encode(this.toPrimitive().toString());
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
fromHash(hash: string) : EntityId<T> {
|
|
36
|
+
protected fromHash(hash: string) : EntityId<T> {
|
|
37
37
|
if (!hash) {
|
|
38
38
|
return this;
|
|
39
39
|
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import {InMemoryEventBus} from "./eventBus/InMemoryEventBus";
|
|
2
|
+
import {DomainEventSubscriber} from "./DomainEventSubscriber";
|
|
3
|
+
import {DomainEvent, DomainEventClass} from "./DomainEvent";
|
|
4
|
+
import {EntityId} from "./EntityId";
|
|
5
|
+
import {Entity} from "./Entity";
|
|
6
|
+
|
|
7
|
+
describe('EventBus', () => {
|
|
8
|
+
class NewUserCreated extends DomainEvent<{ username: string }> {
|
|
9
|
+
}
|
|
10
|
+
class NewUserCreated2 extends DomainEvent<{ username: string }> {
|
|
11
|
+
static EVENT_NAME = 'Event2';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
class NewUserSubscriber implements DomainEventSubscriber {
|
|
15
|
+
subscribedTo(): DomainEventClass[] {
|
|
16
|
+
return [NewUserCreated, NewUserCreated2];
|
|
17
|
+
}
|
|
18
|
+
async on(domainEvent: NewUserCreated|NewUserCreated2): Promise<void> {
|
|
19
|
+
console.log('Event received:', domainEvent);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
test('lifecycle', async () => {
|
|
24
|
+
const newUserSubscriber = new NewUserSubscriber();
|
|
25
|
+
newUserSubscriber.on = jest.fn();
|
|
26
|
+
const hub = new InMemoryEventBus();
|
|
27
|
+
hub.addSubscribers([newUserSubscriber]);
|
|
28
|
+
|
|
29
|
+
const event = new NewUserCreated({ username: 'test' });
|
|
30
|
+
const event2 = new NewUserCreated2({ username: 'test2' });
|
|
31
|
+
|
|
32
|
+
expect(event.eventName).toEqual('NewUserCreated');
|
|
33
|
+
expect(event2.eventName).toEqual('Event2');
|
|
34
|
+
|
|
35
|
+
await hub.publish([event, event2]);
|
|
36
|
+
|
|
37
|
+
expect(newUserSubscriber.on).toHaveBeenCalledTimes(2);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('user example', async () => {
|
|
41
|
+
class UserId extends EntityId<string> {}
|
|
42
|
+
|
|
43
|
+
interface IUser {
|
|
44
|
+
id: UserId;
|
|
45
|
+
username: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
class User extends Entity<IUser, string, UserId> {
|
|
49
|
+
static create(props: IUser): User {
|
|
50
|
+
return new User(props, props.id);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
set username(username: string) {
|
|
54
|
+
this.props.username = username;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
get username(): string {
|
|
58
|
+
return this.props.username;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
class UserCreatedEvent extends DomainEvent<Partial<IUser>> {
|
|
63
|
+
readonly eventName = 'UserCreatedEvent'; // без цього не буде працювати статична типізація
|
|
64
|
+
|
|
65
|
+
get username() {
|
|
66
|
+
return this.props.username;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
class ChangeLoggerSubscriber implements DomainEventSubscriber {
|
|
71
|
+
// Підписуємося на події, які нас цікавлять
|
|
72
|
+
subscribedTo() {
|
|
73
|
+
return [UserCreatedEvent];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Реалізуємо логіку обробки подій
|
|
77
|
+
async on(domainEvent: UserCreatedEvent) {
|
|
78
|
+
console.log('Username changed:', domainEvent.username);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Далі створюємо EventBus і реєструємо підписника
|
|
83
|
+
const eventBus = new InMemoryEventBus();
|
|
84
|
+
const subscriber = new ChangeLoggerSubscriber();
|
|
85
|
+
const mockOn = jest.spyOn(subscriber, 'on');
|
|
86
|
+
eventBus.addSubscribers([subscriber]);
|
|
87
|
+
|
|
88
|
+
const user = User.create({ id: new UserId('1'), username: 'john_doe' });
|
|
89
|
+
// Створюємо знімок початкового стану
|
|
90
|
+
user.snapshot();
|
|
91
|
+
// Змінюємо ім'я користувача
|
|
92
|
+
user.username = 'john_doe_updated';
|
|
93
|
+
let event;
|
|
94
|
+
// Перевіряємо, чи є зміни
|
|
95
|
+
if (user.isDirty()) {
|
|
96
|
+
const diff = user.snapshotDiff();
|
|
97
|
+
if (diff) {
|
|
98
|
+
event = new UserCreatedEvent(diff);
|
|
99
|
+
// записуємо подію в чергу (не publish). Бо якщо в циклі може бути багато подій і їх треба назбирати, а потім виконати, тому збираємо через push
|
|
100
|
+
eventBus.push(event);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
expect(mockOn).not.toHaveBeenCalled();
|
|
104
|
+
// Публікуємо всі події в черзі
|
|
105
|
+
await eventBus.publish();
|
|
106
|
+
expect(mockOn).toHaveBeenCalledWith(event);
|
|
107
|
+
});
|
|
108
|
+
});
|
package/src/EventBus.ts
CHANGED
|
@@ -2,6 +2,8 @@ import {AnyDomainEvent} from './DomainEvent';
|
|
|
2
2
|
import {DomainEventSubscriber} from "./DomainEventSubscriber";
|
|
3
3
|
|
|
4
4
|
export interface EventBus {
|
|
5
|
+
push(event: AnyDomainEvent): Promise<void>;
|
|
6
|
+
clear(): Promise<void>;
|
|
5
7
|
publish(events: AnyDomainEvent[]): Promise<void>;
|
|
6
8
|
addSubscribers(subscribers: DomainEventSubscriber[]): void;
|
|
7
9
|
}
|
|
@@ -12,12 +12,8 @@ class TestAggregate extends AggregateRoot<ITestAggregate, number, EntityId<numbe
|
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
class TestEvent extends DomainEvent<
|
|
15
|
+
class TestEvent extends DomainEvent<TestAggregate> {
|
|
16
16
|
static EVENT_NAME = 'TestEvent';
|
|
17
|
-
|
|
18
|
-
toPrimitives() {
|
|
19
|
-
return {};
|
|
20
|
-
}
|
|
21
17
|
}
|
|
22
18
|
|
|
23
19
|
class TestSubscriber implements DomainEventSubscriber {
|
|
@@ -4,17 +4,29 @@ import { EventBus } from '../EventBus';
|
|
|
4
4
|
import {DomainEventSubscriber} from "../DomainEventSubscriber";
|
|
5
5
|
|
|
6
6
|
export class InMemoryEventBus extends EventEmitter implements EventBus {
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
protected _events: AnyDomainEvent[] = [];
|
|
8
|
+
|
|
9
|
+
async push(event: AnyDomainEvent): Promise<void> {
|
|
10
|
+
this._events.push(event);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async clear(): Promise<void> {
|
|
14
|
+
this._events = [];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async publish(events: AnyDomainEvent[] = []): Promise<void> {
|
|
18
|
+
const allEvents = (this._events || []).concat(events ?? []);
|
|
19
|
+
for (const event of allEvents) {
|
|
9
20
|
this.emit(event.eventName, event);
|
|
10
21
|
}
|
|
22
|
+
await this.clear();
|
|
11
23
|
}
|
|
12
24
|
|
|
13
25
|
addSubscribers(subscribers: DomainEventSubscriber[]): void {
|
|
14
26
|
for (const subscriber of subscribers) {
|
|
15
27
|
const events = subscriber.subscribedTo();
|
|
16
28
|
for (const event of events) {
|
|
17
|
-
this.on(event.EVENT_NAME, subscriber.on.bind(subscriber));
|
|
29
|
+
this.on(event.EVENT_NAME ?? event.name, subscriber.on.bind(subscriber));
|
|
18
30
|
}
|
|
19
31
|
}
|
|
20
32
|
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import {AggregateRoot} from "./AggregateRoot";
|
|
2
|
-
import {EntityId} from "./EntityId";
|
|
3
|
-
import {DomainEvent} from "./DomainEvent";
|
|
4
|
-
|
|
5
|
-
interface TestProps {
|
|
6
|
-
name: string;
|
|
7
|
-
}
|
|
8
|
-
class TestEntity extends AggregateRoot<TestProps, number, EntityId<number>> {
|
|
9
|
-
static create(props: TestProps, id?:EntityId<number>|number): TestEntity {
|
|
10
|
-
return new TestEntity(props, id);
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
class TestEvent extends DomainEvent<number> {
|
|
14
|
-
readonly eventName = 'TestEvent';
|
|
15
|
-
|
|
16
|
-
toPrimitives() {
|
|
17
|
-
return {};
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
describe('AggregateRoot', () => {
|
|
22
|
-
let root: TestEntity;
|
|
23
|
-
|
|
24
|
-
beforeAll(() => {
|
|
25
|
-
root = TestEntity.create({ name: 'test' }, 1);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
test('events', async () => {
|
|
29
|
-
const event = new TestEvent(root);
|
|
30
|
-
root.pushDomainEvent(event);
|
|
31
|
-
|
|
32
|
-
expect(root.pullDomainEvents()).toEqual([event]);
|
|
33
|
-
expect(root.pullDomainEvents()).toEqual([]);
|
|
34
|
-
});
|
|
35
|
-
});
|