diff --git a/src/Contexts/Mooc/Courses/domain/CourseCreatedDomainEvent.ts b/src/Contexts/Mooc/Courses/domain/CourseCreatedDomainEvent.ts index 1d07f55..2745366 100644 --- a/src/Contexts/Mooc/Courses/domain/CourseCreatedDomainEvent.ts +++ b/src/Contexts/Mooc/Courses/domain/CourseCreatedDomainEvent.ts @@ -13,9 +13,9 @@ export class CourseCreatedDomainEvent extends DomainEvent { constructor({ id, - eventId, - duration, name, + duration, + eventId, occurredOn }: { id: string; @@ -42,7 +42,7 @@ export class CourseCreatedDomainEvent extends DomainEvent { body: CreateCourseDomainEventBody, eventId: string, occurredOn: Date - ): CourseCreatedDomainEvent { + ): DomainEvent { return new CourseCreatedDomainEvent({ id: aggregateId, duration: body.duration, diff --git a/src/Contexts/Mooc/CoursesCounter/application/Increment/IncrementCoursesCounterOnCourseCreated.ts b/src/Contexts/Mooc/CoursesCounter/application/Increment/IncrementCoursesCounterOnCourseCreated.ts index 88aa04f..eb4271b 100644 --- a/src/Contexts/Mooc/CoursesCounter/application/Increment/IncrementCoursesCounterOnCourseCreated.ts +++ b/src/Contexts/Mooc/CoursesCounter/application/Increment/IncrementCoursesCounterOnCourseCreated.ts @@ -1,13 +1,14 @@ +import { DomainEventClass } from '../../../../Shared/domain/DomainEvent'; import { DomainEventSubscriber } from '../../../../Shared/domain/DomainEventSubscriber'; import { CourseCreatedDomainEvent } from '../../../Courses/domain/CourseCreatedDomainEvent'; -import { CoursesCounterIncrementer } from './CoursesCounterIncrementer'; import { CourseId } from '../../../Shared/domain/Courses/CourseId'; +import { CoursesCounterIncrementer } from './CoursesCounterIncrementer'; export class IncrementCoursesCounterOnCourseCreated implements DomainEventSubscriber { constructor(private incrementer: CoursesCounterIncrementer) {} - subscribedTo(): string[] { - return [CourseCreatedDomainEvent.EVENT_NAME]; + subscribedTo(): DomainEventClass[] { + return [CourseCreatedDomainEvent]; } async on(domainEvent: CourseCreatedDomainEvent) { diff --git a/src/Contexts/Shared/domain/DomainEvent.ts b/src/Contexts/Shared/domain/DomainEvent.ts index 42aa327..93729ae 100644 --- a/src/Contexts/Shared/domain/DomainEvent.ts +++ b/src/Contexts/Shared/domain/DomainEvent.ts @@ -1,6 +1,8 @@ import { Uuid } from './value-object/Uuid'; export abstract class DomainEvent { + static EVENT_NAME: string; + static fromPrimitives: (...args: any[]) => any; readonly aggregateId: string; readonly eventId: string; readonly occurredOn: Date; @@ -15,3 +17,5 @@ export abstract class DomainEvent { abstract toPrimitive(): Object; } + +export type DomainEventClass = { EVENT_NAME: string, fromPrimitives(...args: any[]): DomainEvent; }; diff --git a/src/Contexts/Shared/domain/DomainEventSubscriber.ts b/src/Contexts/Shared/domain/DomainEventSubscriber.ts index d1eea04..931771e 100644 --- a/src/Contexts/Shared/domain/DomainEventSubscriber.ts +++ b/src/Contexts/Shared/domain/DomainEventSubscriber.ts @@ -1,7 +1,7 @@ -import { DomainEvent } from './DomainEvent'; +import { DomainEvent, DomainEventClass } from './DomainEvent'; export interface DomainEventSubscriber { - subscribedTo(): Array; + subscribedTo(): Array; on(domainEvent: T): Promise; } diff --git a/src/Contexts/Shared/infrastructure/EventBus/DomainEventJsonDeserializer.ts b/src/Contexts/Shared/infrastructure/EventBus/DomainEventJsonDeserializer.ts new file mode 100644 index 0000000..66cce83 --- /dev/null +++ b/src/Contexts/Shared/infrastructure/EventBus/DomainEventJsonDeserializer.ts @@ -0,0 +1,27 @@ +import { DomainEvent } from '../../domain/DomainEvent'; +import { DomainEventMapping } from './DomainEventMapping'; + +export class DomainEventJsonDeserializer { + private mapping: DomainEventMapping; + + constructor(mapping: DomainEventMapping) { + this.mapping = mapping; + } + + deserialize(event: string): DomainEvent { + const eventData = JSON.parse(event).data; + const eventName = eventData.type; + const eventClass = this.mapping.for(eventName); + + if (!eventClass) { + throw new Error(`The event ${eventName} doesn't exist or has no subscribers`); + } + + return eventClass.fromPrimitives( + eventData.attributes.id, + eventData.attributes, + eventData.id, + eventData.occurred_on + ); + } +} diff --git a/src/Contexts/Shared/infrastructure/EventBus/DomainEventMapping.ts b/src/Contexts/Shared/infrastructure/EventBus/DomainEventMapping.ts new file mode 100644 index 0000000..cdcd229 --- /dev/null +++ b/src/Contexts/Shared/infrastructure/EventBus/DomainEventMapping.ts @@ -0,0 +1,36 @@ +import { DomainEventClass, DomainEvent } from '../../domain/DomainEvent'; +import { DomainEventSubscriber } from '../../domain/DomainEventSubscriber'; + +type Mapping = Map; + +export class DomainEventMapping { + mapping: Mapping; + + constructor(mapping: DomainEventSubscriber[]) { + this.mapping = mapping.reduce(this.eventsExtractor(), new Map()); + } + + private eventsExtractor() { + return (map: Mapping, subscriber: DomainEventSubscriber) => { + subscriber.subscribedTo().forEach(this.eventNameExtractor(map)); + return map; + }; + } + + private eventNameExtractor(map: Mapping): (domainEvent: DomainEventClass) => void { + return domainEvent => { + const eventName = domainEvent.EVENT_NAME; + map.set(eventName, domainEvent); + }; + } + + for(name: string): DomainEventClass { + const domainEvent = this.mapping.get(name); + + if (!domainEvent) { + throw new Error(`The Domain Event Class for ${name} doesn't exists or have no subscribers`); + } + + return domainEvent; + } +} diff --git a/src/Contexts/Shared/infrastructure/EventBus/EventEmitterBus.ts b/src/Contexts/Shared/infrastructure/EventBus/EventEmitterBus.ts index 73dc763..4cf309f 100644 --- a/src/Contexts/Shared/infrastructure/EventBus/EventEmitterBus.ts +++ b/src/Contexts/Shared/infrastructure/EventBus/EventEmitterBus.ts @@ -1,6 +1,6 @@ +import { EventEmitter } from 'events'; import { DomainEvent } from '../../domain/DomainEvent'; import { DomainEventSubscriber } from '../../domain/DomainEventSubscriber'; -import { EventEmitter } from 'events'; export class EventEmitterBus extends EventEmitter { constructor(subscribers: Array>) { @@ -17,7 +17,7 @@ export class EventEmitterBus extends EventEmitter { private registerSubscriber(subscriber: DomainEventSubscriber) { subscriber.subscribedTo().map(event => { - this.on(event, subscriber.on); + this.on(event.EVENT_NAME, subscriber.on); }); } diff --git a/src/Contexts/Shared/infrastructure/EventBus/InMemorySyncEventBus.ts b/src/Contexts/Shared/infrastructure/EventBus/InMemorySyncEventBus.ts index b31c481..4d30d1d 100644 --- a/src/Contexts/Shared/infrastructure/EventBus/InMemorySyncEventBus.ts +++ b/src/Contexts/Shared/infrastructure/EventBus/InMemorySyncEventBus.ts @@ -1,6 +1,6 @@ -import { EventBus } from '../../domain/EventBus'; import { DomainEvent } from '../../domain/DomainEvent'; import { DomainEventSubscriber } from '../../domain/DomainEventSubscriber'; +import { EventBus } from '../../domain/EventBus'; type Subscription = { boundedCallback: Function; @@ -27,7 +27,9 @@ export class InMemorySyncEventBus implements EventBus { } addSubscribers(subscribers: Array>) { - subscribers.map(subscriber => subscriber.subscribedTo().map(event => this.subscribe(event, subscriber))); + subscribers.map(subscriber => + subscriber.subscribedTo().map(event => this.subscribe(event.EVENT_NAME!, subscriber)) + ); } private subscribe(topic: string, subscriber: DomainEventSubscriber): void { diff --git a/src/apps/mooc_backend/config/dependency-injection/Shared/application.yaml b/src/apps/mooc_backend/config/dependency-injection/Shared/application.yaml index 718a0d9..62137a3 100644 --- a/src/apps/mooc_backend/config/dependency-injection/Shared/application.yaml +++ b/src/apps/mooc_backend/config/dependency-injection/Shared/application.yaml @@ -1,5 +1,4 @@ services: - Mooc.shared.ConnectionManager: factory: class: ../../../../../Contexts/Shared/infrastructure/persistence/mongo/MongoClientFactory @@ -13,3 +12,11 @@ services: Mooc.shared.EventBus: class: ../../../../../Contexts/Shared/infrastructure/EventBus/InMemoryAsyncEventBus arguments: [] + + Mooc.shared.EventBus.DomainEventMapping: + class: ../../../../../Contexts/Shared/infrastructure/EventBus/DomainEventMapping + arguments: ['!tagged domainEventSubscriber'] + + Mooc.shared.EventBus.DomainEventJsonDeserializer: + class: ../../../../../Contexts/Shared/infrastructure/EventBus/DomainEventJsonDeserializer + arguments: ['@Mooc.shared.EventBus.DomainEventMapping'] diff --git a/tests/Contexts/Shared/infrastructure/InMemoryAsyncEventBus.test.ts b/tests/Contexts/Shared/infrastructure/InMemoryAsyncEventBus.test.ts index db84c02..f753b56 100644 --- a/tests/Contexts/Shared/infrastructure/InMemoryAsyncEventBus.test.ts +++ b/tests/Contexts/Shared/infrastructure/InMemoryAsyncEventBus.test.ts @@ -1,7 +1,7 @@ -import { InMemoryAsyncEventBus } from '../../../../src/Contexts/Shared/infrastructure/EventBus/InMemoryAsyncEventBus'; -import { DomainEventSubscriber } from '../../../../src/Contexts/Shared/domain/DomainEventSubscriber'; import { DomainEvent } from '../../../../src/Contexts/Shared/domain/DomainEvent'; +import { DomainEventSubscriber } from '../../../../src/Contexts/Shared/domain/DomainEventSubscriber'; import { Uuid } from '../../../../src/Contexts/Shared/domain/value-object/Uuid'; +import { InMemoryAsyncEventBus } from '../../../../src/Contexts/Shared/infrastructure/EventBus/InMemoryAsyncEventBus'; describe('InMemoryAsyncEventBus', () => { let subscriber: DomainEventSubscriberDummy; @@ -35,8 +35,8 @@ class DummyEvent extends DomainEvent { } class DomainEventSubscriberDummy implements DomainEventSubscriber { - subscribedTo(): string[] { - return [DummyEvent.EVENT_NAME]; + subscribedTo(): any[] { + return [DummyEvent]; } async on(domainEvent: DummyEvent) { diff --git a/tests/apps/mooc_backend/features/step_definitions/evenBus.steps.ts b/tests/apps/mooc_backend/features/step_definitions/evenBus.steps.ts index b8e4631..751ff59 100644 --- a/tests/apps/mooc_backend/features/step_definitions/evenBus.steps.ts +++ b/tests/apps/mooc_backend/features/step_definitions/evenBus.steps.ts @@ -1,19 +1,13 @@ import { Given } from 'cucumber'; import container from '../../../../../src/apps/mooc_backend/config/dependency-injection'; import { EventBus } from '../../../../../src/Contexts/Shared/domain/EventBus'; -import { CourseCreatedDomainEvent } from '../../../../../src/Contexts/Mooc/Courses/domain/CourseCreatedDomainEvent'; +import { DomainEventJsonDeserializer } from '../../../../../src/Contexts/Shared/infrastructure/EventBus/DomainEventJsonDeserializer'; -Given('I send an event to the event bus:', async (event: any) => { - const eventBus = container.get('Mooc.shared.EventBus') as EventBus; - const jsonEvent = JSON.parse(event).data; +const eventBus = container.get('Mooc.shared.EventBus') as EventBus; +const deserializer = container.get('Mooc.shared.EventBus.DomainEventJsonDeserializer') as DomainEventJsonDeserializer; - const domainEvent = CourseCreatedDomainEvent.fromPrimitives( - jsonEvent.attributes.id, - jsonEvent.attributes, - jsonEvent.id, - jsonEvent.occurred_on - ); +Given('I send an event to the event bus:', async (event: any) => { + const domainEvent = deserializer.deserialize(event); await eventBus.publish([domainEvent]); }); -