event-store-adapter-js 1.1.99-snapshot.1 → 2.0.0
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.ja.md +2 -2
- package/README.md +2 -0
- package/dist/event-store-with-options.d.ts +6 -0
- package/dist/event-store-with-options.js +2 -0
- package/dist/event-store.d.ts +6 -6
- package/dist/event-store.js +6 -2
- package/dist/internal/event-store-for-dynamodb.d.ts +12 -10
- package/dist/internal/event-store-for-dynamodb.js +27 -18
- package/dist/internal/event-store-for-memory.d.ts +12 -0
- package/dist/internal/event-store-for-memory.js +92 -0
- package/package.json +1 -1
- package/src/event-store-with-options.ts +12 -0
- package/src/event-store.ts +17 -8
- package/src/internal/event-store-for-dynamodb.test.ts +21 -13
- package/src/internal/event-store-for-dynamodb.ts +46 -18
- package/src/internal/event-store-for-memory.test.ts +89 -0
- package/src/internal/event-store-for-memory.ts +99 -0
- package/src/internal/test/user-account-repository.test.ts +7 -2
- package/src/internal/test/user-account-repository.ts +3 -10
package/README.ja.md
CHANGED
|
@@ -41,7 +41,6 @@ class UserAccountRepository {
|
|
|
41
41
|
async findById(id: UserAccountId): Promise<UserAccount | undefined> {
|
|
42
42
|
const snapshot = await this.eventStore.getLatestSnapshotById(
|
|
43
43
|
id,
|
|
44
|
-
convertJSONToUserAccount,
|
|
45
44
|
);
|
|
46
45
|
if (snapshot === undefined) {
|
|
47
46
|
return undefined;
|
|
@@ -49,7 +48,6 @@ class UserAccountRepository {
|
|
|
49
48
|
const events = await this.eventStore.getEventsByIdSinceSequenceNumber(
|
|
50
49
|
id,
|
|
51
50
|
snapshot.sequenceNumber + 1,
|
|
52
|
-
convertJSONtoUserAccountEvent,
|
|
53
51
|
);
|
|
54
52
|
return UserAccount.replay(events, snapshot);
|
|
55
53
|
}
|
|
@@ -71,6 +69,8 @@ const eventStore = EventStoreFactory.ofDynamoDB<
|
|
|
71
69
|
JOURNAL_AID_INDEX_NAME,
|
|
72
70
|
SNAPSHOTS_AID_INDEX_NAME,
|
|
73
71
|
32,
|
|
72
|
+
convertJSONtoUserAccountEvent,
|
|
73
|
+
convertJSONToUserAccount,
|
|
74
74
|
);
|
|
75
75
|
const userAccountRepository = new UserAccountRepository(eventStore);
|
|
76
76
|
|
package/README.md
CHANGED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Aggregate, AggregateId, Event } from "./types";
|
|
2
|
+
import { EventStoreOptions } from "./event-store-options";
|
|
3
|
+
import { EventStore } from "./event-store";
|
|
4
|
+
interface EventStoreWithOptions<AID extends AggregateId, A extends Aggregate<A, AID>, E extends Event<AID>> extends EventStore<AID, A, E>, EventStoreOptions<EventStoreWithOptions<AID, A, E>, AID, A, E> {
|
|
5
|
+
}
|
|
6
|
+
export { EventStoreWithOptions };
|
package/dist/event-store.d.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { Aggregate, AggregateId, Event, EventSerializer, KeyResolver, SnapshotSerializer } from "./types";
|
|
2
|
-
import { EventStoreOptions } from "./event-store-options";
|
|
3
|
-
import { EventStoreForDynamoDB } from "./internal/event-store-for-dynamodb";
|
|
4
2
|
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
|
|
5
3
|
import moment from "moment";
|
|
6
|
-
|
|
4
|
+
import { EventStoreWithOptions } from "./event-store-with-options";
|
|
5
|
+
interface EventStore<AID extends AggregateId, A extends Aggregate<A, AID>, E extends Event<AID>> {
|
|
7
6
|
persistEvent(event: E, version: number): Promise<void>;
|
|
8
7
|
persistEventAndSnapshot(event: E, aggregate: A): Promise<void>;
|
|
9
|
-
getEventsByIdSinceSequenceNumber(id: AID, sequenceNumber: number
|
|
10
|
-
getLatestSnapshotById(id: AID
|
|
8
|
+
getEventsByIdSinceSequenceNumber(id: AID, sequenceNumber: number): Promise<E[]>;
|
|
9
|
+
getLatestSnapshotById(id: AID): Promise<A | undefined>;
|
|
11
10
|
}
|
|
12
11
|
declare class EventStoreFactory {
|
|
13
|
-
static ofDynamoDB<AID extends AggregateId, A extends Aggregate<A, AID>, E extends Event<AID>>(dynamodbClient: DynamoDBClient, journalTableName: string, snapshotTableName: string, journalAidIndexName: string, snapshotAidIndexName: string, shardCount: number, keepSnapshotCount?: number | undefined, deleteTtl?: moment.Duration | undefined, keyResolver?: KeyResolver<AID>, eventSerializer?: EventSerializer<AID, E>, snapshotSerializer?: SnapshotSerializer<AID, A>):
|
|
12
|
+
static ofDynamoDB<AID extends AggregateId, A extends Aggregate<A, AID>, E extends Event<AID>>(dynamodbClient: DynamoDBClient, journalTableName: string, snapshotTableName: string, journalAidIndexName: string, snapshotAidIndexName: string, shardCount: number, eventConverter: (json: string) => E, snapshotConverter: (json: string) => A, keepSnapshotCount?: number | undefined, deleteTtl?: moment.Duration | undefined, keyResolver?: KeyResolver<AID>, eventSerializer?: EventSerializer<AID, E>, snapshotSerializer?: SnapshotSerializer<AID, A>): EventStoreWithOptions<AID, A, E>;
|
|
13
|
+
static ofMemory<AID extends AggregateId, A extends Aggregate<A, AID>, E extends Event<AID>>(events: Map<AID, E[]>, snapshots: Map<AID, A>): EventStore<AID, A, E>;
|
|
14
14
|
}
|
|
15
15
|
export { EventStore, EventStoreFactory };
|
package/dist/event-store.js
CHANGED
|
@@ -4,9 +4,13 @@ exports.EventStoreFactory = void 0;
|
|
|
4
4
|
const event_store_for_dynamodb_1 = require("./internal/event-store-for-dynamodb");
|
|
5
5
|
const default_key_resolver_1 = require("./internal/default-key-resolver");
|
|
6
6
|
const default_serializer_1 = require("./internal/default-serializer");
|
|
7
|
+
const event_store_for_memory_1 = require("./internal/event-store-for-memory");
|
|
7
8
|
class EventStoreFactory {
|
|
8
|
-
static ofDynamoDB(dynamodbClient, journalTableName, snapshotTableName, journalAidIndexName, snapshotAidIndexName, shardCount, keepSnapshotCount = undefined, deleteTtl = undefined, keyResolver = new default_key_resolver_1.DefaultKeyResolver(), eventSerializer = new default_serializer_1.JsonEventSerializer(), snapshotSerializer = new default_serializer_1.JsonSnapshotSerializer()) {
|
|
9
|
-
return new event_store_for_dynamodb_1.EventStoreForDynamoDB(dynamodbClient, journalTableName, snapshotTableName, journalAidIndexName, snapshotAidIndexName, shardCount, keepSnapshotCount, deleteTtl, keyResolver, eventSerializer, snapshotSerializer);
|
|
9
|
+
static ofDynamoDB(dynamodbClient, journalTableName, snapshotTableName, journalAidIndexName, snapshotAidIndexName, shardCount, eventConverter, snapshotConverter, keepSnapshotCount = undefined, deleteTtl = undefined, keyResolver = new default_key_resolver_1.DefaultKeyResolver(), eventSerializer = new default_serializer_1.JsonEventSerializer(), snapshotSerializer = new default_serializer_1.JsonSnapshotSerializer()) {
|
|
10
|
+
return new event_store_for_dynamodb_1.EventStoreForDynamoDB(dynamodbClient, journalTableName, snapshotTableName, journalAidIndexName, snapshotAidIndexName, shardCount, eventConverter, snapshotConverter, keepSnapshotCount, deleteTtl, keyResolver, eventSerializer, snapshotSerializer);
|
|
11
|
+
}
|
|
12
|
+
static ofMemory(events, snapshots) {
|
|
13
|
+
return new event_store_for_memory_1.EventStoreForMemory(events, snapshots);
|
|
10
14
|
}
|
|
11
15
|
}
|
|
12
16
|
exports.EventStoreFactory = EventStoreFactory;
|
|
@@ -1,30 +1,32 @@
|
|
|
1
1
|
import { Aggregate, AggregateId, Event, EventSerializer, KeyResolver, Logger, SnapshotSerializer } from "../types";
|
|
2
|
-
import { EventStore } from "../event-store";
|
|
3
2
|
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
|
|
4
3
|
import moment from "moment/moment";
|
|
5
|
-
|
|
4
|
+
import { EventStoreWithOptions } from "../event-store-with-options";
|
|
5
|
+
declare class EventStoreForDynamoDB<AID extends AggregateId, A extends Aggregate<A, AID>, E extends Event<AID>> implements EventStoreWithOptions<AID, A, E> {
|
|
6
6
|
private dynamodbClient;
|
|
7
7
|
private journalTableName;
|
|
8
8
|
private snapshotTableName;
|
|
9
9
|
private journalAidIndexName;
|
|
10
10
|
private snapshotAidIndexName;
|
|
11
11
|
private shardCount;
|
|
12
|
+
private eventConverter;
|
|
13
|
+
private snapshotConverter;
|
|
12
14
|
private keepSnapshotCount;
|
|
13
15
|
private deleteTtl;
|
|
14
16
|
private keyResolver;
|
|
15
17
|
private eventSerializer;
|
|
16
18
|
private snapshotSerializer;
|
|
17
19
|
private logger;
|
|
18
|
-
constructor(dynamodbClient: DynamoDBClient, journalTableName: string, snapshotTableName: string, journalAidIndexName: string, snapshotAidIndexName: string, shardCount: number, keepSnapshotCount?: number | undefined, deleteTtl?: moment.Duration | undefined, keyResolver?: KeyResolver<AID>, eventSerializer?: EventSerializer<AID, E>, snapshotSerializer?: SnapshotSerializer<AID, A>, logger?: Logger | undefined);
|
|
19
|
-
getEventsByIdSinceSequenceNumber(id: AID, sequenceNumber: number
|
|
20
|
-
getLatestSnapshotById(id: AID
|
|
20
|
+
constructor(dynamodbClient: DynamoDBClient, journalTableName: string, snapshotTableName: string, journalAidIndexName: string, snapshotAidIndexName: string, shardCount: number, eventConverter: (json: string) => E, snapshotConverter: (json: string) => A, keepSnapshotCount?: number | undefined, deleteTtl?: moment.Duration | undefined, keyResolver?: KeyResolver<AID>, eventSerializer?: EventSerializer<AID, E>, snapshotSerializer?: SnapshotSerializer<AID, A>, logger?: Logger | undefined);
|
|
21
|
+
getEventsByIdSinceSequenceNumber(id: AID, sequenceNumber: number): Promise<E[]>;
|
|
22
|
+
getLatestSnapshotById(id: AID): Promise<A | undefined>;
|
|
21
23
|
persistEvent(event: E, version: number): Promise<void>;
|
|
22
24
|
persistEventAndSnapshot(event: E, aggregate: A): Promise<void>;
|
|
23
|
-
withDeleteTtl(deleteTtl: moment.Duration):
|
|
24
|
-
withEventSerializer(eventSerializer: EventSerializer<AID, E>):
|
|
25
|
-
withKeepSnapshotCount(keepSnapshotCount: number):
|
|
26
|
-
withKeyResolver(keyResolver: KeyResolver<AID>):
|
|
27
|
-
withSnapshotSerializer(snapshotSerializer: SnapshotSerializer<AID, A>):
|
|
25
|
+
withDeleteTtl(deleteTtl: moment.Duration): EventStoreWithOptions<AID, A, E>;
|
|
26
|
+
withEventSerializer(eventSerializer: EventSerializer<AID, E>): EventStoreWithOptions<AID, A, E>;
|
|
27
|
+
withKeepSnapshotCount(keepSnapshotCount: number): EventStoreWithOptions<AID, A, E>;
|
|
28
|
+
withKeyResolver(keyResolver: KeyResolver<AID>): EventStoreWithOptions<AID, A, E>;
|
|
29
|
+
withSnapshotSerializer(snapshotSerializer: SnapshotSerializer<AID, A>): EventStoreWithOptions<AID, A, E>;
|
|
28
30
|
private createEventAndSnapshot;
|
|
29
31
|
private updateEventAndSnapshotOpt;
|
|
30
32
|
private putJournal;
|
|
@@ -18,13 +18,15 @@ const moment_1 = __importDefault(require("moment/moment"));
|
|
|
18
18
|
const default_key_resolver_1 = require("./default-key-resolver");
|
|
19
19
|
const default_serializer_1 = require("./default-serializer");
|
|
20
20
|
class EventStoreForDynamoDB {
|
|
21
|
-
constructor(dynamodbClient, journalTableName, snapshotTableName, journalAidIndexName, snapshotAidIndexName, shardCount, keepSnapshotCount = undefined, deleteTtl = undefined, keyResolver = new default_key_resolver_1.DefaultKeyResolver(), eventSerializer = new default_serializer_1.JsonEventSerializer(), snapshotSerializer = new default_serializer_1.JsonSnapshotSerializer(), logger = undefined) {
|
|
21
|
+
constructor(dynamodbClient, journalTableName, snapshotTableName, journalAidIndexName, snapshotAidIndexName, shardCount, eventConverter, snapshotConverter, keepSnapshotCount = undefined, deleteTtl = undefined, keyResolver = new default_key_resolver_1.DefaultKeyResolver(), eventSerializer = new default_serializer_1.JsonEventSerializer(), snapshotSerializer = new default_serializer_1.JsonSnapshotSerializer(), logger = undefined) {
|
|
22
22
|
this.dynamodbClient = dynamodbClient;
|
|
23
23
|
this.journalTableName = journalTableName;
|
|
24
24
|
this.snapshotTableName = snapshotTableName;
|
|
25
25
|
this.journalAidIndexName = journalAidIndexName;
|
|
26
26
|
this.snapshotAidIndexName = snapshotAidIndexName;
|
|
27
27
|
this.shardCount = shardCount;
|
|
28
|
+
this.eventConverter = eventConverter;
|
|
29
|
+
this.snapshotConverter = snapshotConverter;
|
|
28
30
|
this.keepSnapshotCount = keepSnapshotCount;
|
|
29
31
|
this.deleteTtl = deleteTtl;
|
|
30
32
|
this.keyResolver = keyResolver;
|
|
@@ -32,7 +34,7 @@ class EventStoreForDynamoDB {
|
|
|
32
34
|
this.snapshotSerializer = snapshotSerializer;
|
|
33
35
|
this.logger = logger;
|
|
34
36
|
}
|
|
35
|
-
getEventsByIdSinceSequenceNumber(id, sequenceNumber
|
|
37
|
+
getEventsByIdSinceSequenceNumber(id, sequenceNumber) {
|
|
36
38
|
var _a, _b;
|
|
37
39
|
return __awaiter(this, void 0, void 0, function* () {
|
|
38
40
|
(_a = this.logger) === null || _a === void 0 ? void 0 : _a.debug(`getEventsByIdSinceSequenceNumber(${JSON.stringify(id)}, ${sequenceNumber}, ...): start`);
|
|
@@ -60,14 +62,14 @@ class EventStoreForDynamoDB {
|
|
|
60
62
|
if (payload === undefined) {
|
|
61
63
|
throw new Error("Payload is undefined");
|
|
62
64
|
}
|
|
63
|
-
return this.eventSerializer.deserialize(payload,
|
|
65
|
+
return this.eventSerializer.deserialize(payload, this.eventConverter);
|
|
64
66
|
});
|
|
65
67
|
}
|
|
66
68
|
(_b = this.logger) === null || _b === void 0 ? void 0 : _b.debug(`getEventsByIdSinceSequenceNumber(${JSON.stringify(id)}, ${sequenceNumber}, ...): finished`);
|
|
67
69
|
return result;
|
|
68
70
|
});
|
|
69
71
|
}
|
|
70
|
-
getLatestSnapshotById(id
|
|
72
|
+
getLatestSnapshotById(id) {
|
|
71
73
|
var _a, _b;
|
|
72
74
|
return __awaiter(this, void 0, void 0, function* () {
|
|
73
75
|
(_a = this.logger) === null || _a === void 0 ? void 0 : _a.debug(`getLatestSnapshotById(${JSON.stringify(id)}, ...): start`);
|
|
@@ -99,7 +101,7 @@ class EventStoreForDynamoDB {
|
|
|
99
101
|
if (payload === undefined) {
|
|
100
102
|
throw new Error("Payload is undefined");
|
|
101
103
|
}
|
|
102
|
-
const result = this.snapshotSerializer.deserialize(payload,
|
|
104
|
+
const result = this.snapshotSerializer.deserialize(payload, this.snapshotConverter);
|
|
103
105
|
(_b = this.logger) === null || _b === void 0 ? void 0 : _b.debug(`getLatestSnapshotById(${JSON.stringify(id)}, ...): finished`);
|
|
104
106
|
return result.withVersion(Number(version));
|
|
105
107
|
}
|
|
@@ -120,6 +122,9 @@ class EventStoreForDynamoDB {
|
|
|
120
122
|
persistEventAndSnapshot(event, aggregate) {
|
|
121
123
|
var _a, _b;
|
|
122
124
|
return __awaiter(this, void 0, void 0, function* () {
|
|
125
|
+
if (event.aggregateId.asString !== aggregate.id.asString) {
|
|
126
|
+
throw new Error(`aggregateId mismatch: expected ${event.aggregateId.asString}, got ${aggregate.id.asString}`);
|
|
127
|
+
}
|
|
123
128
|
(_a = this.logger) === null || _a === void 0 ? void 0 : _a.debug(`persistEventAndSnapshot(${JSON.stringify(event)}, ${JSON.stringify(aggregate)}): start`);
|
|
124
129
|
if (event.isCreated) {
|
|
125
130
|
yield this.createEventAndSnapshot(event, aggregate);
|
|
@@ -132,19 +137,19 @@ class EventStoreForDynamoDB {
|
|
|
132
137
|
});
|
|
133
138
|
}
|
|
134
139
|
withDeleteTtl(deleteTtl) {
|
|
135
|
-
return new EventStoreForDynamoDB(this.dynamodbClient, this.journalTableName, this.snapshotTableName, this.journalAidIndexName, this.snapshotAidIndexName, this.shardCount, this.keepSnapshotCount, deleteTtl, this.keyResolver, this.eventSerializer, this.snapshotSerializer);
|
|
140
|
+
return new EventStoreForDynamoDB(this.dynamodbClient, this.journalTableName, this.snapshotTableName, this.journalAidIndexName, this.snapshotAidIndexName, this.shardCount, this.eventConverter, this.snapshotConverter, this.keepSnapshotCount, deleteTtl, this.keyResolver, this.eventSerializer, this.snapshotSerializer);
|
|
136
141
|
}
|
|
137
142
|
withEventSerializer(eventSerializer) {
|
|
138
|
-
return new EventStoreForDynamoDB(this.dynamodbClient, this.journalTableName, this.snapshotTableName, this.journalAidIndexName, this.snapshotAidIndexName, this.shardCount, this.keepSnapshotCount, this.deleteTtl, this.keyResolver, eventSerializer, this.snapshotSerializer);
|
|
143
|
+
return new EventStoreForDynamoDB(this.dynamodbClient, this.journalTableName, this.snapshotTableName, this.journalAidIndexName, this.snapshotAidIndexName, this.shardCount, this.eventConverter, this.snapshotConverter, this.keepSnapshotCount, this.deleteTtl, this.keyResolver, eventSerializer, this.snapshotSerializer);
|
|
139
144
|
}
|
|
140
145
|
withKeepSnapshotCount(keepSnapshotCount) {
|
|
141
|
-
return new EventStoreForDynamoDB(this.dynamodbClient, this.journalTableName, this.snapshotTableName, this.journalAidIndexName, this.snapshotAidIndexName, this.shardCount, keepSnapshotCount, this.deleteTtl, this.keyResolver, this.eventSerializer, this.snapshotSerializer);
|
|
146
|
+
return new EventStoreForDynamoDB(this.dynamodbClient, this.journalTableName, this.snapshotTableName, this.journalAidIndexName, this.snapshotAidIndexName, this.shardCount, this.eventConverter, this.snapshotConverter, keepSnapshotCount, this.deleteTtl, this.keyResolver, this.eventSerializer, this.snapshotSerializer);
|
|
142
147
|
}
|
|
143
148
|
withKeyResolver(keyResolver) {
|
|
144
|
-
return new EventStoreForDynamoDB(this.dynamodbClient, this.journalTableName, this.snapshotTableName, this.journalAidIndexName, this.snapshotAidIndexName, this.shardCount, this.keepSnapshotCount, this.deleteTtl, keyResolver, this.eventSerializer, this.snapshotSerializer);
|
|
149
|
+
return new EventStoreForDynamoDB(this.dynamodbClient, this.journalTableName, this.snapshotTableName, this.journalAidIndexName, this.snapshotAidIndexName, this.shardCount, this.eventConverter, this.snapshotConverter, this.keepSnapshotCount, this.deleteTtl, keyResolver, this.eventSerializer, this.snapshotSerializer);
|
|
145
150
|
}
|
|
146
151
|
withSnapshotSerializer(snapshotSerializer) {
|
|
147
|
-
return new EventStoreForDynamoDB(this.dynamodbClient, this.journalTableName, this.snapshotTableName, this.journalAidIndexName, this.snapshotAidIndexName, this.shardCount, this.keepSnapshotCount, this.deleteTtl, this.keyResolver, this.eventSerializer, snapshotSerializer);
|
|
152
|
+
return new EventStoreForDynamoDB(this.dynamodbClient, this.journalTableName, this.snapshotTableName, this.journalAidIndexName, this.snapshotAidIndexName, this.shardCount, this.eventConverter, this.snapshotConverter, this.keepSnapshotCount, this.deleteTtl, this.keyResolver, this.eventSerializer, snapshotSerializer);
|
|
148
153
|
}
|
|
149
154
|
createEventAndSnapshot(event, aggregate) {
|
|
150
155
|
var _a, _b;
|
|
@@ -379,7 +384,9 @@ class EventStoreForDynamoDB {
|
|
|
379
384
|
":ttl": { N: ttl.seconds().toString() },
|
|
380
385
|
},
|
|
381
386
|
};
|
|
382
|
-
return this.dynamodbClient
|
|
387
|
+
return this.dynamodbClient
|
|
388
|
+
.send(new client_dynamodb_1.UpdateItemCommand(request))
|
|
389
|
+
.then((_) => { });
|
|
383
390
|
});
|
|
384
391
|
return yield Promise.all(result);
|
|
385
392
|
}
|
|
@@ -401,7 +408,7 @@ class EventStoreForDynamoDB {
|
|
|
401
408
|
if (keys === undefined) {
|
|
402
409
|
return undefined;
|
|
403
410
|
}
|
|
404
|
-
const
|
|
411
|
+
const request = keys.map((key) => {
|
|
405
412
|
const request = {
|
|
406
413
|
DeleteRequest: {
|
|
407
414
|
Key: {
|
|
@@ -410,13 +417,15 @@ class EventStoreForDynamoDB {
|
|
|
410
417
|
},
|
|
411
418
|
},
|
|
412
419
|
};
|
|
413
|
-
return
|
|
414
|
-
RequestItems: {
|
|
415
|
-
[this.snapshotTableName]: [request],
|
|
416
|
-
},
|
|
417
|
-
}));
|
|
420
|
+
return request;
|
|
418
421
|
});
|
|
419
|
-
return
|
|
422
|
+
return this.dynamodbClient
|
|
423
|
+
.send(new client_dynamodb_1.BatchWriteItemCommand({
|
|
424
|
+
RequestItems: {
|
|
425
|
+
[this.snapshotTableName]: request,
|
|
426
|
+
},
|
|
427
|
+
}))
|
|
428
|
+
.then((_) => { });
|
|
420
429
|
}
|
|
421
430
|
}
|
|
422
431
|
return undefined;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Aggregate, AggregateId, Event } from "../types";
|
|
2
|
+
import { EventStore } from "../event-store";
|
|
3
|
+
declare class EventStoreForMemory<AID extends AggregateId, A extends Aggregate<A, AID>, E extends Event<AID>> implements EventStore<AID, A, E> {
|
|
4
|
+
private readonly events;
|
|
5
|
+
private readonly snapshots;
|
|
6
|
+
constructor(events: Map<AID, E[]>, snapshots: Map<AID, A>);
|
|
7
|
+
persistEvent(event: E, version: number): Promise<void>;
|
|
8
|
+
persistEventAndSnapshot(event: E, aggregate: A): Promise<void>;
|
|
9
|
+
getEventsByIdSinceSequenceNumber(id: AID, sequenceNumber: number): Promise<E[]>;
|
|
10
|
+
getLatestSnapshotById(id: AID): Promise<A | undefined>;
|
|
11
|
+
}
|
|
12
|
+
export { EventStoreForMemory };
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.EventStoreForMemory = void 0;
|
|
13
|
+
class EventStoreForMemory {
|
|
14
|
+
constructor(events, snapshots) {
|
|
15
|
+
this.events = new Map(Array.from(events).map(([key, values]) => {
|
|
16
|
+
return [key.asString, values];
|
|
17
|
+
}));
|
|
18
|
+
this.snapshots = new Map(Array.from(snapshots).map(([key, value]) => {
|
|
19
|
+
return [key.asString, value];
|
|
20
|
+
}));
|
|
21
|
+
}
|
|
22
|
+
persistEvent(event, version) {
|
|
23
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
24
|
+
if (event.isCreated) {
|
|
25
|
+
throw new Error("event is created");
|
|
26
|
+
}
|
|
27
|
+
const aggregateIdString = event.aggregateId.asString;
|
|
28
|
+
const snapshot = this.snapshots.get(aggregateIdString);
|
|
29
|
+
if (snapshot === undefined) {
|
|
30
|
+
throw new Error("snapshot is undefined");
|
|
31
|
+
}
|
|
32
|
+
if (snapshot.id.asString !== event.aggregateId.asString) {
|
|
33
|
+
throw new Error("aggregateId mismatch: snapshot.id = " +
|
|
34
|
+
snapshot.id.asString +
|
|
35
|
+
", event.aggregateId = " +
|
|
36
|
+
event.aggregateId.asString);
|
|
37
|
+
}
|
|
38
|
+
if (snapshot.version !== version) {
|
|
39
|
+
throw new Error("version mismatch");
|
|
40
|
+
}
|
|
41
|
+
const events = this.events.get(aggregateIdString);
|
|
42
|
+
if (events === undefined) {
|
|
43
|
+
throw new Error("events is undefined");
|
|
44
|
+
}
|
|
45
|
+
events.push(event);
|
|
46
|
+
this.events.set(aggregateIdString, events);
|
|
47
|
+
const newVersion = snapshot.version + 1;
|
|
48
|
+
const newSnapshot = snapshot.withVersion(newVersion);
|
|
49
|
+
this.snapshots.set(aggregateIdString, newSnapshot);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
persistEventAndSnapshot(event, aggregate) {
|
|
53
|
+
var _a, _b;
|
|
54
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
55
|
+
if (event.aggregateId.asString !== aggregate.id.asString) {
|
|
56
|
+
throw new Error(`aggregateId mismatch: expected ${event.aggregateId.asString}, got ${aggregate.id.asString}`);
|
|
57
|
+
}
|
|
58
|
+
const aggregateIdString = event.aggregateId.asString;
|
|
59
|
+
const events = (_a = this.events.get(aggregateIdString)) !== null && _a !== void 0 ? _a : [];
|
|
60
|
+
const snapshot = (_b = this.snapshots.get(aggregateIdString)) !== null && _b !== void 0 ? _b : aggregate;
|
|
61
|
+
let newVersion = 1;
|
|
62
|
+
if (!event.isCreated) {
|
|
63
|
+
const version = snapshot.version;
|
|
64
|
+
if (version !== aggregate.version) {
|
|
65
|
+
throw new Error("version mismatch");
|
|
66
|
+
}
|
|
67
|
+
newVersion = snapshot.version + 1;
|
|
68
|
+
}
|
|
69
|
+
events.push(event);
|
|
70
|
+
this.events.set(aggregateIdString, events);
|
|
71
|
+
const newSnapshot = snapshot.withVersion(newVersion);
|
|
72
|
+
this.snapshots.set(aggregateIdString, newSnapshot);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
getEventsByIdSinceSequenceNumber(id, sequenceNumber) {
|
|
76
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
77
|
+
const aggregateIdString = id.asString;
|
|
78
|
+
const events = this.events.get(aggregateIdString);
|
|
79
|
+
if (events === undefined) {
|
|
80
|
+
throw new Error("events is undefined");
|
|
81
|
+
}
|
|
82
|
+
return events.filter((event) => event.sequenceNumber >= sequenceNumber);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
getLatestSnapshotById(id) {
|
|
86
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
87
|
+
const aggregateIdString = id.asString;
|
|
88
|
+
return this.snapshots.get(aggregateIdString);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
exports.EventStoreForMemory = EventStoreForMemory;
|
package/package.json
CHANGED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Aggregate, AggregateId, Event } from "./types";
|
|
2
|
+
import { EventStoreOptions } from "./event-store-options";
|
|
3
|
+
import { EventStore } from "./event-store";
|
|
4
|
+
|
|
5
|
+
interface EventStoreWithOptions<
|
|
6
|
+
AID extends AggregateId,
|
|
7
|
+
A extends Aggregate<A, AID>,
|
|
8
|
+
E extends Event<AID>,
|
|
9
|
+
> extends EventStore<AID, A, E>,
|
|
10
|
+
EventStoreOptions<EventStoreWithOptions<AID, A, E>, AID, A, E> {}
|
|
11
|
+
|
|
12
|
+
export { EventStoreWithOptions };
|
package/src/event-store.ts
CHANGED
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
KeyResolver,
|
|
7
7
|
SnapshotSerializer,
|
|
8
8
|
} from "./types";
|
|
9
|
-
import { EventStoreOptions } from "./event-store-options";
|
|
10
9
|
import { EventStoreForDynamoDB } from "./internal/event-store-for-dynamodb";
|
|
11
10
|
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
|
|
12
11
|
import moment from "moment";
|
|
@@ -15,23 +14,21 @@ import {
|
|
|
15
14
|
JsonEventSerializer,
|
|
16
15
|
JsonSnapshotSerializer,
|
|
17
16
|
} from "./internal/default-serializer";
|
|
17
|
+
import { EventStoreForMemory } from "./internal/event-store-for-memory";
|
|
18
|
+
import { EventStoreWithOptions } from "./event-store-with-options";
|
|
18
19
|
|
|
19
20
|
interface EventStore<
|
|
20
21
|
AID extends AggregateId,
|
|
21
22
|
A extends Aggregate<A, AID>,
|
|
22
23
|
E extends Event<AID>,
|
|
23
|
-
>
|
|
24
|
+
> {
|
|
24
25
|
persistEvent(event: E, version: number): Promise<void>;
|
|
25
26
|
persistEventAndSnapshot(event: E, aggregate: A): Promise<void>;
|
|
26
27
|
getEventsByIdSinceSequenceNumber(
|
|
27
28
|
id: AID,
|
|
28
29
|
sequenceNumber: number,
|
|
29
|
-
converter: (json: string) => E,
|
|
30
30
|
): Promise<E[]>;
|
|
31
|
-
getLatestSnapshotById(
|
|
32
|
-
id: AID,
|
|
33
|
-
converter: (json: string) => A,
|
|
34
|
-
): Promise<A | undefined>;
|
|
31
|
+
getLatestSnapshotById(id: AID): Promise<A | undefined>;
|
|
35
32
|
}
|
|
36
33
|
|
|
37
34
|
class EventStoreFactory {
|
|
@@ -46,6 +43,8 @@ class EventStoreFactory {
|
|
|
46
43
|
journalAidIndexName: string,
|
|
47
44
|
snapshotAidIndexName: string,
|
|
48
45
|
shardCount: number,
|
|
46
|
+
eventConverter: (json: string) => E,
|
|
47
|
+
snapshotConverter: (json: string) => A,
|
|
49
48
|
keepSnapshotCount: number | undefined = undefined,
|
|
50
49
|
deleteTtl: moment.Duration | undefined = undefined,
|
|
51
50
|
keyResolver: KeyResolver<AID> = new DefaultKeyResolver(),
|
|
@@ -57,7 +56,7 @@ class EventStoreFactory {
|
|
|
57
56
|
AID,
|
|
58
57
|
A
|
|
59
58
|
>(),
|
|
60
|
-
) {
|
|
59
|
+
): EventStoreWithOptions<AID, A, E> {
|
|
61
60
|
return new EventStoreForDynamoDB<AID, A, E>(
|
|
62
61
|
dynamodbClient,
|
|
63
62
|
journalTableName,
|
|
@@ -65,6 +64,8 @@ class EventStoreFactory {
|
|
|
65
64
|
journalAidIndexName,
|
|
66
65
|
snapshotAidIndexName,
|
|
67
66
|
shardCount,
|
|
67
|
+
eventConverter,
|
|
68
|
+
snapshotConverter,
|
|
68
69
|
keepSnapshotCount,
|
|
69
70
|
deleteTtl,
|
|
70
71
|
keyResolver,
|
|
@@ -72,6 +73,14 @@ class EventStoreFactory {
|
|
|
72
73
|
snapshotSerializer,
|
|
73
74
|
);
|
|
74
75
|
}
|
|
76
|
+
|
|
77
|
+
static ofMemory<
|
|
78
|
+
AID extends AggregateId,
|
|
79
|
+
A extends Aggregate<A, AID>,
|
|
80
|
+
E extends Event<AID>,
|
|
81
|
+
>(events: Map<AID, E[]>, snapshots: Map<AID, A>): EventStore<AID, A, E> {
|
|
82
|
+
return new EventStoreForMemory(events, snapshots);
|
|
83
|
+
}
|
|
75
84
|
}
|
|
76
85
|
|
|
77
86
|
export { EventStore, EventStoreFactory };
|
|
@@ -10,7 +10,10 @@ import { EventStoreForDynamoDB } from "./event-store-for-dynamodb";
|
|
|
10
10
|
import { ulid } from "ulid";
|
|
11
11
|
import { UserAccountId } from "./test/user-account-id";
|
|
12
12
|
import { convertJSONToUserAccount, UserAccount } from "./test/user-account";
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
convertJSONtoUserAccountEvent,
|
|
15
|
+
UserAccountEvent,
|
|
16
|
+
} from "./test/user-account-event";
|
|
14
17
|
import {
|
|
15
18
|
createDynamoDBClient,
|
|
16
19
|
createJournalTable,
|
|
@@ -52,6 +55,8 @@ describe("EventStoreForDynamoDB", () => {
|
|
|
52
55
|
JOURNAL_AID_INDEX_NAME,
|
|
53
56
|
SNAPSHOTS_AID_INDEX_NAME,
|
|
54
57
|
32,
|
|
58
|
+
convertJSONtoUserAccountEvent,
|
|
59
|
+
convertJSONToUserAccount,
|
|
55
60
|
);
|
|
56
61
|
}
|
|
57
62
|
|
|
@@ -96,10 +101,7 @@ describe("EventStoreForDynamoDB", () => {
|
|
|
96
101
|
|
|
97
102
|
await eventStore.persistEventAndSnapshot(created, userAccount1);
|
|
98
103
|
|
|
99
|
-
const userAccount2 = await eventStore.getLatestSnapshotById(
|
|
100
|
-
id,
|
|
101
|
-
convertJSONToUserAccount,
|
|
102
|
-
);
|
|
104
|
+
const userAccount2 = await eventStore.getLatestSnapshotById(id);
|
|
103
105
|
if (userAccount2 === undefined) {
|
|
104
106
|
throw new Error("userAccount2 is undefined");
|
|
105
107
|
}
|
|
@@ -123,17 +125,23 @@ describe("EventStoreForDynamoDB", () => {
|
|
|
123
125
|
|
|
124
126
|
await eventStore.persistEvent(renamed, userAccount2.version);
|
|
125
127
|
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
);
|
|
130
|
-
if (userAccount3 === undefined) {
|
|
131
|
-
throw new Error("userAccount2 is undefined");
|
|
128
|
+
const latestSnapshot = await eventStore.getLatestSnapshotById(id);
|
|
129
|
+
if (latestSnapshot === undefined) {
|
|
130
|
+
throw new Error("latestSnapshot is undefined");
|
|
132
131
|
}
|
|
132
|
+
const eventsAfterSnapshot =
|
|
133
|
+
await eventStore.getEventsByIdSinceSequenceNumber(
|
|
134
|
+
id,
|
|
135
|
+
latestSnapshot.sequenceNumber + 1,
|
|
136
|
+
);
|
|
137
|
+
const userAccount3 = UserAccount.replay(
|
|
138
|
+
eventsAfterSnapshot,
|
|
139
|
+
latestSnapshot,
|
|
140
|
+
);
|
|
133
141
|
|
|
134
142
|
expect(userAccount3.id).toEqual(id);
|
|
135
|
-
expect(userAccount3.name).toEqual(
|
|
136
|
-
expect(userAccount3.sequenceNumber).toEqual(
|
|
143
|
+
expect(userAccount3.name).toEqual("Bob");
|
|
144
|
+
expect(userAccount3.sequenceNumber).toEqual(2);
|
|
137
145
|
expect(userAccount3.version).toEqual(2);
|
|
138
146
|
},
|
|
139
147
|
TIMEOUT,
|
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
Logger,
|
|
8
8
|
SnapshotSerializer,
|
|
9
9
|
} from "../types";
|
|
10
|
-
import { EventStore } from "../event-store";
|
|
11
10
|
import {
|
|
12
11
|
BatchWriteItemCommand,
|
|
13
12
|
DynamoDBClient,
|
|
@@ -27,12 +26,13 @@ import {
|
|
|
27
26
|
JsonEventSerializer,
|
|
28
27
|
JsonSnapshotSerializer,
|
|
29
28
|
} from "./default-serializer";
|
|
29
|
+
import { EventStoreWithOptions } from "../event-store-with-options";
|
|
30
30
|
|
|
31
31
|
class EventStoreForDynamoDB<
|
|
32
32
|
AID extends AggregateId,
|
|
33
33
|
A extends Aggregate<A, AID>,
|
|
34
34
|
E extends Event<AID>,
|
|
35
|
-
> implements
|
|
35
|
+
> implements EventStoreWithOptions<AID, A, E>
|
|
36
36
|
{
|
|
37
37
|
constructor(
|
|
38
38
|
private dynamodbClient: DynamoDBClient,
|
|
@@ -41,6 +41,8 @@ class EventStoreForDynamoDB<
|
|
|
41
41
|
private journalAidIndexName: string,
|
|
42
42
|
private snapshotAidIndexName: string,
|
|
43
43
|
private shardCount: number,
|
|
44
|
+
private eventConverter: (json: string) => E,
|
|
45
|
+
private snapshotConverter: (json: string) => A,
|
|
44
46
|
private keepSnapshotCount: number | undefined = undefined,
|
|
45
47
|
private deleteTtl: moment.Duration | undefined = undefined,
|
|
46
48
|
private keyResolver: KeyResolver<AID> = new DefaultKeyResolver(),
|
|
@@ -58,7 +60,7 @@ class EventStoreForDynamoDB<
|
|
|
58
60
|
async getEventsByIdSinceSequenceNumber(
|
|
59
61
|
id: AID,
|
|
60
62
|
sequenceNumber: number,
|
|
61
|
-
converter: (json: string) => E,
|
|
63
|
+
// converter: (json: string) => E,
|
|
62
64
|
): Promise<E[]> {
|
|
63
65
|
this.logger?.debug(
|
|
64
66
|
`getEventsByIdSinceSequenceNumber(${JSON.stringify(
|
|
@@ -90,7 +92,7 @@ class EventStoreForDynamoDB<
|
|
|
90
92
|
if (payload === undefined) {
|
|
91
93
|
throw new Error("Payload is undefined");
|
|
92
94
|
}
|
|
93
|
-
return this.eventSerializer.deserialize(payload,
|
|
95
|
+
return this.eventSerializer.deserialize(payload, this.eventConverter);
|
|
94
96
|
});
|
|
95
97
|
}
|
|
96
98
|
this.logger?.debug(
|
|
@@ -103,7 +105,7 @@ class EventStoreForDynamoDB<
|
|
|
103
105
|
|
|
104
106
|
async getLatestSnapshotById(
|
|
105
107
|
id: AID,
|
|
106
|
-
converter: (json: string) => A,
|
|
108
|
+
// converter: (json: string) => A,
|
|
107
109
|
): Promise<A | undefined> {
|
|
108
110
|
this.logger?.debug(
|
|
109
111
|
`getLatestSnapshotById(${JSON.stringify(id)}, ...): start`,
|
|
@@ -137,7 +139,10 @@ class EventStoreForDynamoDB<
|
|
|
137
139
|
if (payload === undefined) {
|
|
138
140
|
throw new Error("Payload is undefined");
|
|
139
141
|
}
|
|
140
|
-
const result = this.snapshotSerializer.deserialize(
|
|
142
|
+
const result = this.snapshotSerializer.deserialize(
|
|
143
|
+
payload,
|
|
144
|
+
this.snapshotConverter,
|
|
145
|
+
);
|
|
141
146
|
this.logger?.debug(
|
|
142
147
|
`getLatestSnapshotById(${JSON.stringify(id)}, ...): finished`,
|
|
143
148
|
);
|
|
@@ -160,6 +165,11 @@ class EventStoreForDynamoDB<
|
|
|
160
165
|
}
|
|
161
166
|
|
|
162
167
|
async persistEventAndSnapshot(event: E, aggregate: A): Promise<void> {
|
|
168
|
+
if (event.aggregateId.asString !== aggregate.id.asString) {
|
|
169
|
+
throw new Error(
|
|
170
|
+
`aggregateId mismatch: expected ${event.aggregateId.asString}, got ${aggregate.id.asString}`,
|
|
171
|
+
);
|
|
172
|
+
}
|
|
163
173
|
this.logger?.debug(
|
|
164
174
|
`persistEventAndSnapshot(${JSON.stringify(event)}, ${JSON.stringify(
|
|
165
175
|
aggregate,
|
|
@@ -182,7 +192,7 @@ class EventStoreForDynamoDB<
|
|
|
182
192
|
);
|
|
183
193
|
}
|
|
184
194
|
|
|
185
|
-
withDeleteTtl(deleteTtl: moment.Duration):
|
|
195
|
+
withDeleteTtl(deleteTtl: moment.Duration): EventStoreWithOptions<AID, A, E> {
|
|
186
196
|
return new EventStoreForDynamoDB(
|
|
187
197
|
this.dynamodbClient,
|
|
188
198
|
this.journalTableName,
|
|
@@ -190,6 +200,8 @@ class EventStoreForDynamoDB<
|
|
|
190
200
|
this.journalAidIndexName,
|
|
191
201
|
this.snapshotAidIndexName,
|
|
192
202
|
this.shardCount,
|
|
203
|
+
this.eventConverter,
|
|
204
|
+
this.snapshotConverter,
|
|
193
205
|
this.keepSnapshotCount,
|
|
194
206
|
deleteTtl,
|
|
195
207
|
this.keyResolver,
|
|
@@ -200,7 +212,7 @@ class EventStoreForDynamoDB<
|
|
|
200
212
|
|
|
201
213
|
withEventSerializer(
|
|
202
214
|
eventSerializer: EventSerializer<AID, E>,
|
|
203
|
-
):
|
|
215
|
+
): EventStoreWithOptions<AID, A, E> {
|
|
204
216
|
return new EventStoreForDynamoDB(
|
|
205
217
|
this.dynamodbClient,
|
|
206
218
|
this.journalTableName,
|
|
@@ -208,6 +220,8 @@ class EventStoreForDynamoDB<
|
|
|
208
220
|
this.journalAidIndexName,
|
|
209
221
|
this.snapshotAidIndexName,
|
|
210
222
|
this.shardCount,
|
|
223
|
+
this.eventConverter,
|
|
224
|
+
this.snapshotConverter,
|
|
211
225
|
this.keepSnapshotCount,
|
|
212
226
|
this.deleteTtl,
|
|
213
227
|
this.keyResolver,
|
|
@@ -216,7 +230,9 @@ class EventStoreForDynamoDB<
|
|
|
216
230
|
);
|
|
217
231
|
}
|
|
218
232
|
|
|
219
|
-
withKeepSnapshotCount(
|
|
233
|
+
withKeepSnapshotCount(
|
|
234
|
+
keepSnapshotCount: number,
|
|
235
|
+
): EventStoreWithOptions<AID, A, E> {
|
|
220
236
|
return new EventStoreForDynamoDB(
|
|
221
237
|
this.dynamodbClient,
|
|
222
238
|
this.journalTableName,
|
|
@@ -224,6 +240,8 @@ class EventStoreForDynamoDB<
|
|
|
224
240
|
this.journalAidIndexName,
|
|
225
241
|
this.snapshotAidIndexName,
|
|
226
242
|
this.shardCount,
|
|
243
|
+
this.eventConverter,
|
|
244
|
+
this.snapshotConverter,
|
|
227
245
|
keepSnapshotCount,
|
|
228
246
|
this.deleteTtl,
|
|
229
247
|
this.keyResolver,
|
|
@@ -232,7 +250,9 @@ class EventStoreForDynamoDB<
|
|
|
232
250
|
);
|
|
233
251
|
}
|
|
234
252
|
|
|
235
|
-
withKeyResolver(
|
|
253
|
+
withKeyResolver(
|
|
254
|
+
keyResolver: KeyResolver<AID>,
|
|
255
|
+
): EventStoreWithOptions<AID, A, E> {
|
|
236
256
|
return new EventStoreForDynamoDB(
|
|
237
257
|
this.dynamodbClient,
|
|
238
258
|
this.journalTableName,
|
|
@@ -240,6 +260,8 @@ class EventStoreForDynamoDB<
|
|
|
240
260
|
this.journalAidIndexName,
|
|
241
261
|
this.snapshotAidIndexName,
|
|
242
262
|
this.shardCount,
|
|
263
|
+
this.eventConverter,
|
|
264
|
+
this.snapshotConverter,
|
|
243
265
|
this.keepSnapshotCount,
|
|
244
266
|
this.deleteTtl,
|
|
245
267
|
keyResolver,
|
|
@@ -250,7 +272,7 @@ class EventStoreForDynamoDB<
|
|
|
250
272
|
|
|
251
273
|
withSnapshotSerializer(
|
|
252
274
|
snapshotSerializer: SnapshotSerializer<AID, A>,
|
|
253
|
-
):
|
|
275
|
+
): EventStoreWithOptions<AID, A, E> {
|
|
254
276
|
return new EventStoreForDynamoDB(
|
|
255
277
|
this.dynamodbClient,
|
|
256
278
|
this.journalTableName,
|
|
@@ -258,6 +280,8 @@ class EventStoreForDynamoDB<
|
|
|
258
280
|
this.journalAidIndexName,
|
|
259
281
|
this.snapshotAidIndexName,
|
|
260
282
|
this.shardCount,
|
|
283
|
+
this.eventConverter,
|
|
284
|
+
this.snapshotConverter,
|
|
261
285
|
this.keepSnapshotCount,
|
|
262
286
|
this.deleteTtl,
|
|
263
287
|
this.keyResolver,
|
|
@@ -571,7 +595,9 @@ class EventStoreForDynamoDB<
|
|
|
571
595
|
":ttl": { N: ttl.seconds().toString() },
|
|
572
596
|
},
|
|
573
597
|
};
|
|
574
|
-
return this.dynamodbClient
|
|
598
|
+
return this.dynamodbClient
|
|
599
|
+
.send(new UpdateItemCommand(request))
|
|
600
|
+
.then((_) => {});
|
|
575
601
|
});
|
|
576
602
|
return await Promise.all(result);
|
|
577
603
|
}
|
|
@@ -592,7 +618,7 @@ class EventStoreForDynamoDB<
|
|
|
592
618
|
if (keys === undefined) {
|
|
593
619
|
return undefined;
|
|
594
620
|
}
|
|
595
|
-
const
|
|
621
|
+
const request = keys.map((key) => {
|
|
596
622
|
const request: WriteRequest = {
|
|
597
623
|
DeleteRequest: {
|
|
598
624
|
Key: {
|
|
@@ -601,15 +627,17 @@ class EventStoreForDynamoDB<
|
|
|
601
627
|
},
|
|
602
628
|
},
|
|
603
629
|
};
|
|
604
|
-
return
|
|
630
|
+
return request;
|
|
631
|
+
});
|
|
632
|
+
return this.dynamodbClient
|
|
633
|
+
.send(
|
|
605
634
|
new BatchWriteItemCommand({
|
|
606
635
|
RequestItems: {
|
|
607
|
-
[this.snapshotTableName]:
|
|
636
|
+
[this.snapshotTableName]: request,
|
|
608
637
|
},
|
|
609
638
|
}),
|
|
610
|
-
)
|
|
611
|
-
|
|
612
|
-
return await Promise.all(result);
|
|
639
|
+
)
|
|
640
|
+
.then((_) => {});
|
|
613
641
|
}
|
|
614
642
|
}
|
|
615
643
|
return undefined;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { describe } from "node:test";
|
|
2
|
+
import { UserAccountId } from "./test/user-account-id";
|
|
3
|
+
// import {ulid} from "ulid";
|
|
4
|
+
import { UserAccount } from "./test/user-account";
|
|
5
|
+
import { UserAccountEvent } from "./test/user-account-event";
|
|
6
|
+
import { EventStore } from "../event-store";
|
|
7
|
+
import { EventStoreForMemory } from "./event-store-for-memory";
|
|
8
|
+
|
|
9
|
+
afterEach(() => {
|
|
10
|
+
jest.useRealTimers();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
describe("EventStoreForDynamoDB", () => {
|
|
14
|
+
const TEST_TIME_FACTOR = parseFloat(process.env.TEST_TIME_FACTOR ?? "1.0");
|
|
15
|
+
const TIMEOUT: number = 10 * 1000 * TEST_TIME_FACTOR;
|
|
16
|
+
|
|
17
|
+
let eventStore: EventStore<UserAccountId, UserAccount, UserAccountEvent>;
|
|
18
|
+
|
|
19
|
+
function createEventStore(): EventStore<
|
|
20
|
+
UserAccountId,
|
|
21
|
+
UserAccount,
|
|
22
|
+
UserAccountEvent
|
|
23
|
+
> {
|
|
24
|
+
return new EventStoreForMemory<
|
|
25
|
+
UserAccountId,
|
|
26
|
+
UserAccount,
|
|
27
|
+
UserAccountEvent
|
|
28
|
+
>(new Map(), new Map());
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
beforeAll(async () => {
|
|
32
|
+
eventStore = createEventStore();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test(
|
|
36
|
+
"persistAndSnapshot",
|
|
37
|
+
async () => {
|
|
38
|
+
const id = new UserAccountId("1");
|
|
39
|
+
const name = "Alice";
|
|
40
|
+
const [userAccount1, created] = UserAccount.create(id, name);
|
|
41
|
+
|
|
42
|
+
await eventStore.persistEventAndSnapshot(created, userAccount1);
|
|
43
|
+
|
|
44
|
+
const userAccount2 = await eventStore.getLatestSnapshotById(id);
|
|
45
|
+
if (userAccount2 === undefined) {
|
|
46
|
+
throw new Error("userAccount2 is undefined");
|
|
47
|
+
}
|
|
48
|
+
expect(userAccount2.id).toEqual(id);
|
|
49
|
+
expect(userAccount2.name).toEqual(name);
|
|
50
|
+
expect(userAccount2.version).toEqual(1);
|
|
51
|
+
},
|
|
52
|
+
TIMEOUT,
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
test(
|
|
56
|
+
"persistAndSnapshot2",
|
|
57
|
+
async () => {
|
|
58
|
+
const id = new UserAccountId("2");
|
|
59
|
+
const name = "Alice";
|
|
60
|
+
const [userAccount1, created] = UserAccount.create(id, name);
|
|
61
|
+
|
|
62
|
+
await eventStore.persistEventAndSnapshot(created, userAccount1);
|
|
63
|
+
|
|
64
|
+
const [userAccount2, renamed] = userAccount1.rename("Bob");
|
|
65
|
+
|
|
66
|
+
await eventStore.persistEvent(renamed, userAccount2.version);
|
|
67
|
+
|
|
68
|
+
const latestSnapshot = await eventStore.getLatestSnapshotById(id);
|
|
69
|
+
if (latestSnapshot === undefined) {
|
|
70
|
+
throw new Error("latestSnapshot is undefined");
|
|
71
|
+
}
|
|
72
|
+
const eventsAfterSnapshot =
|
|
73
|
+
await eventStore.getEventsByIdSinceSequenceNumber(
|
|
74
|
+
id,
|
|
75
|
+
latestSnapshot.sequenceNumber + 1,
|
|
76
|
+
);
|
|
77
|
+
const userAccount3 = UserAccount.replay(
|
|
78
|
+
eventsAfterSnapshot,
|
|
79
|
+
latestSnapshot,
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
expect(userAccount3.id).toEqual(id);
|
|
83
|
+
expect(userAccount3.name).toEqual("Bob");
|
|
84
|
+
expect(userAccount3.sequenceNumber).toEqual(2);
|
|
85
|
+
expect(userAccount3.version).toEqual(2);
|
|
86
|
+
},
|
|
87
|
+
TIMEOUT,
|
|
88
|
+
);
|
|
89
|
+
});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { Aggregate, AggregateId, Event } from "../types";
|
|
2
|
+
import { EventStore } from "../event-store";
|
|
3
|
+
|
|
4
|
+
class EventStoreForMemory<
|
|
5
|
+
AID extends AggregateId,
|
|
6
|
+
A extends Aggregate<A, AID>,
|
|
7
|
+
E extends Event<AID>,
|
|
8
|
+
> implements EventStore<AID, A, E>
|
|
9
|
+
{
|
|
10
|
+
private readonly events: Map<string, E[]>;
|
|
11
|
+
private readonly snapshots: Map<string, A>;
|
|
12
|
+
|
|
13
|
+
constructor(events: Map<AID, E[]>, snapshots: Map<AID, A>) {
|
|
14
|
+
this.events = new Map(
|
|
15
|
+
Array.from(events).map(([key, values]) => {
|
|
16
|
+
return [key.asString, values];
|
|
17
|
+
}),
|
|
18
|
+
);
|
|
19
|
+
this.snapshots = new Map(
|
|
20
|
+
Array.from(snapshots).map(([key, value]) => {
|
|
21
|
+
return [key.asString, value];
|
|
22
|
+
}),
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async persistEvent(event: E, version: number): Promise<void> {
|
|
27
|
+
if (event.isCreated) {
|
|
28
|
+
throw new Error("event is created");
|
|
29
|
+
}
|
|
30
|
+
const aggregateIdString = event.aggregateId.asString;
|
|
31
|
+
const snapshot = this.snapshots.get(aggregateIdString);
|
|
32
|
+
if (snapshot === undefined) {
|
|
33
|
+
throw new Error("snapshot is undefined");
|
|
34
|
+
}
|
|
35
|
+
if (snapshot.id.asString !== event.aggregateId.asString) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
"aggregateId mismatch: snapshot.id = " +
|
|
38
|
+
snapshot.id.asString +
|
|
39
|
+
", event.aggregateId = " +
|
|
40
|
+
event.aggregateId.asString,
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
if (snapshot.version !== version) {
|
|
44
|
+
throw new Error("version mismatch");
|
|
45
|
+
}
|
|
46
|
+
const events = this.events.get(aggregateIdString);
|
|
47
|
+
if (events === undefined) {
|
|
48
|
+
throw new Error("events is undefined");
|
|
49
|
+
}
|
|
50
|
+
events.push(event);
|
|
51
|
+
this.events.set(aggregateIdString, events);
|
|
52
|
+
const newVersion = snapshot.version + 1;
|
|
53
|
+
const newSnapshot = snapshot.withVersion(newVersion);
|
|
54
|
+
this.snapshots.set(aggregateIdString, newSnapshot);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async persistEventAndSnapshot(event: E, aggregate: A): Promise<void> {
|
|
58
|
+
if (event.aggregateId.asString !== aggregate.id.asString) {
|
|
59
|
+
throw new Error(
|
|
60
|
+
`aggregateId mismatch: expected ${event.aggregateId.asString}, got ${aggregate.id.asString}`,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
const aggregateIdString = event.aggregateId.asString;
|
|
64
|
+
const events = this.events.get(aggregateIdString) ?? [];
|
|
65
|
+
const snapshot = this.snapshots.get(aggregateIdString) ?? aggregate;
|
|
66
|
+
|
|
67
|
+
let newVersion = 1;
|
|
68
|
+
if (!event.isCreated) {
|
|
69
|
+
const version = snapshot.version;
|
|
70
|
+
if (version !== aggregate.version) {
|
|
71
|
+
throw new Error("version mismatch");
|
|
72
|
+
}
|
|
73
|
+
newVersion = snapshot.version + 1;
|
|
74
|
+
}
|
|
75
|
+
events.push(event);
|
|
76
|
+
this.events.set(aggregateIdString, events);
|
|
77
|
+
const newSnapshot = snapshot.withVersion(newVersion);
|
|
78
|
+
this.snapshots.set(aggregateIdString, newSnapshot);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async getEventsByIdSinceSequenceNumber(
|
|
82
|
+
id: AID,
|
|
83
|
+
sequenceNumber: number,
|
|
84
|
+
): Promise<E[]> {
|
|
85
|
+
const aggregateIdString = id.asString;
|
|
86
|
+
const events = this.events.get(aggregateIdString);
|
|
87
|
+
if (events === undefined) {
|
|
88
|
+
throw new Error("events is undefined");
|
|
89
|
+
}
|
|
90
|
+
return events.filter((event) => event.sequenceNumber >= sequenceNumber);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async getLatestSnapshotById(id: AID): Promise<A | undefined> {
|
|
94
|
+
const aggregateIdString = id.asString;
|
|
95
|
+
return this.snapshots.get(aggregateIdString);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export { EventStoreForMemory };
|
|
@@ -6,8 +6,11 @@ import {
|
|
|
6
6
|
Wait,
|
|
7
7
|
} from "testcontainers";
|
|
8
8
|
import { UserAccountId } from "./user-account-id";
|
|
9
|
-
import { UserAccount } from "./user-account";
|
|
10
|
-
import {
|
|
9
|
+
import { convertJSONToUserAccount, UserAccount } from "./user-account";
|
|
10
|
+
import {
|
|
11
|
+
convertJSONtoUserAccountEvent,
|
|
12
|
+
UserAccountEvent,
|
|
13
|
+
} from "./user-account-event";
|
|
11
14
|
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
|
|
12
15
|
import {
|
|
13
16
|
createDynamoDBClient,
|
|
@@ -49,6 +52,8 @@ describe("UserAccountRepository", () => {
|
|
|
49
52
|
JOURNAL_AID_INDEX_NAME,
|
|
50
53
|
SNAPSHOTS_AID_INDEX_NAME,
|
|
51
54
|
32,
|
|
55
|
+
convertJSONtoUserAccountEvent,
|
|
56
|
+
convertJSONToUserAccount,
|
|
52
57
|
);
|
|
53
58
|
}
|
|
54
59
|
|
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
UserAccountEvent,
|
|
3
|
-
convertJSONtoUserAccountEvent,
|
|
4
|
-
} from "./user-account-event";
|
|
1
|
+
import { UserAccountEvent } from "./user-account-event";
|
|
5
2
|
import { UserAccountId } from "./user-account-id";
|
|
6
|
-
import {
|
|
3
|
+
import { UserAccount } from "./user-account";
|
|
7
4
|
import { EventStore } from "../../event-store";
|
|
8
5
|
|
|
9
6
|
class UserAccountRepository {
|
|
@@ -24,17 +21,13 @@ class UserAccountRepository {
|
|
|
24
21
|
}
|
|
25
22
|
|
|
26
23
|
async findById(id: UserAccountId): Promise<UserAccount | undefined> {
|
|
27
|
-
const snapshot = await this.eventStore.getLatestSnapshotById(
|
|
28
|
-
id,
|
|
29
|
-
convertJSONToUserAccount,
|
|
30
|
-
);
|
|
24
|
+
const snapshot = await this.eventStore.getLatestSnapshotById(id);
|
|
31
25
|
if (snapshot === undefined) {
|
|
32
26
|
return undefined;
|
|
33
27
|
} else {
|
|
34
28
|
const events = await this.eventStore.getEventsByIdSinceSequenceNumber(
|
|
35
29
|
id,
|
|
36
30
|
snapshot.sequenceNumber + 1,
|
|
37
|
-
convertJSONtoUserAccountEvent,
|
|
38
31
|
);
|
|
39
32
|
return UserAccount.replay(events, snapshot);
|
|
40
33
|
}
|