first commit
This commit is contained in:
3
libs/api/shared/application/src/index.ts
Normal file
3
libs/api/shared/application/src/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './lib/ports/event-store';
|
||||
export * from './lib/aggregate-rehydrator';
|
||||
export * from './lib/shared.module';
|
||||
32
libs/api/shared/application/src/lib/aggregate-rehydrator.ts
Normal file
32
libs/api/shared/application/src/lib/aggregate-rehydrator.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { Injectable, Type } from '@nestjs/common';
|
||||
import { EventPublisher } from '@nestjs/cqrs';
|
||||
import {
|
||||
NotFoundError,
|
||||
VersionedAggregateRoot,
|
||||
} from '@task-stream/api/shared/domain';
|
||||
import { EventStore } from './ports/event-store';
|
||||
|
||||
@Injectable()
|
||||
export class AggregateRehydrator {
|
||||
constructor(
|
||||
private readonly eventStore: EventStore,
|
||||
private readonly eventPublisher: EventPublisher,
|
||||
) {}
|
||||
|
||||
async rehydrate<T extends VersionedAggregateRoot>(
|
||||
aggregateId: string,
|
||||
AggregateCls: Type<T>,
|
||||
): Promise<T> {
|
||||
const events = await this.eventStore.getEventsByStreamId(aggregateId);
|
||||
if (events.length === 0) {
|
||||
throw new NotFoundError(AggregateCls.name, aggregateId);
|
||||
}
|
||||
|
||||
const AggregateClsWithDispatcher =
|
||||
this.eventPublisher.mergeClassContext(AggregateCls);
|
||||
const aggregate = new AggregateClsWithDispatcher(aggregateId);
|
||||
|
||||
aggregate.loadFromHistory(events);
|
||||
return aggregate;
|
||||
}
|
||||
}
|
||||
10
libs/api/shared/application/src/lib/ports/event-store.ts
Normal file
10
libs/api/shared/application/src/lib/ports/event-store.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { SerializableEvent } from '@task-stream/api/shared/domain';
|
||||
|
||||
/** Append-only store of domain events (the write model). */
|
||||
export abstract class EventStore {
|
||||
/** Fails with ConcurrencyError if another process appended to the stream first. */
|
||||
abstract persist(
|
||||
eventOrEvents: SerializableEvent | SerializableEvent[],
|
||||
): Promise<void>;
|
||||
abstract getEventsByStreamId(streamId: string): Promise<SerializableEvent[]>;
|
||||
}
|
||||
23
libs/api/shared/application/src/lib/shared.module.ts
Normal file
23
libs/api/shared/application/src/lib/shared.module.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { DynamicModule, Global, Module, Type } from '@nestjs/common';
|
||||
import { CqrsModule } from '@nestjs/cqrs';
|
||||
import { AggregateRehydrator } from './aggregate-rehydrator';
|
||||
|
||||
/**
|
||||
* Shared building blocks for every bounded context: the event store (from
|
||||
* the infrastructure module plugged in by the composition root) and the
|
||||
* AggregateRehydrator.
|
||||
*/
|
||||
@Global()
|
||||
@Module({})
|
||||
export class SharedModule {
|
||||
static withInfrastructure(
|
||||
infrastructureModule: Type | DynamicModule,
|
||||
): DynamicModule {
|
||||
return {
|
||||
module: SharedModule,
|
||||
imports: [CqrsModule, infrastructureModule],
|
||||
providers: [AggregateRehydrator],
|
||||
exports: [infrastructureModule, AggregateRehydrator],
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user