Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/Contexts/Mooc/Courses/domain/CourseCreatedDomainEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ export class CourseCreatedDomainEvent extends DomainEvent {

constructor({
id,
eventId,
duration,
name,
duration,
eventId,
occurredOn
}: {
id: string;
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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<CourseCreatedDomainEvent> {
constructor(private incrementer: CoursesCounterIncrementer) {}

subscribedTo(): string[] {
return [CourseCreatedDomainEvent.EVENT_NAME];
subscribedTo(): DomainEventClass[] {
return [CourseCreatedDomainEvent];
}

async on(domainEvent: CourseCreatedDomainEvent) {
Expand Down
4 changes: 4 additions & 0 deletions src/Contexts/Shared/domain/DomainEvent.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -15,3 +17,5 @@ export abstract class DomainEvent {

abstract toPrimitive(): Object;
}

export type DomainEventClass = { EVENT_NAME: string, fromPrimitives(...args: any[]): DomainEvent; };
4 changes: 2 additions & 2 deletions src/Contexts/Shared/domain/DomainEventSubscriber.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { DomainEvent } from './DomainEvent';
import { DomainEvent, DomainEventClass } from './DomainEvent';

export interface DomainEventSubscriber<T extends DomainEvent> {
subscribedTo(): Array<string>;
subscribedTo(): Array<DomainEventClass>;

on(domainEvent: T): Promise<void>;
}
Original file line number Diff line number Diff line change
@@ -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
);
}
}
36 changes: 36 additions & 0 deletions src/Contexts/Shared/infrastructure/EventBus/DomainEventMapping.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { DomainEventClass, DomainEvent } from '../../domain/DomainEvent';
import { DomainEventSubscriber } from '../../domain/DomainEventSubscriber';

type Mapping = Map<string, DomainEventClass>;

export class DomainEventMapping {
mapping: Mapping;

constructor(mapping: DomainEventSubscriber<DomainEvent>[]) {
this.mapping = mapping.reduce(this.eventsExtractor(), new Map<string, DomainEventClass>());
}

private eventsExtractor() {
return (map: Mapping, subscriber: DomainEventSubscriber<DomainEvent>) => {
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;
}
}
Original file line number Diff line number Diff line change
@@ -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<DomainEventSubscriber<DomainEvent>>) {
Expand All @@ -17,7 +17,7 @@ export class EventEmitterBus extends EventEmitter {

private registerSubscriber(subscriber: DomainEventSubscriber<DomainEvent>) {
subscriber.subscribedTo().map(event => {
this.on(event, subscriber.on);
this.on(event.EVENT_NAME, subscriber.on);
});
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -27,7 +27,9 @@ export class InMemorySyncEventBus implements EventBus {
}

addSubscribers(subscribers: Array<DomainEventSubscriber<DomainEvent>>) {
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<DomainEvent>): void {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
services:

Mooc.shared.ConnectionManager:
factory:
class: ../../../../../Contexts/Shared/infrastructure/persistence/mongo/MongoClientFactory
Expand All @@ -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']
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -35,8 +35,8 @@ class DummyEvent extends DomainEvent {
}

class DomainEventSubscriberDummy implements DomainEventSubscriber<DummyEvent> {
subscribedTo(): string[] {
return [DummyEvent.EVENT_NAME];
subscribedTo(): any[] {
return [DummyEvent];
}

async on(domainEvent: DummyEvent) {
Expand Down
16 changes: 5 additions & 11 deletions tests/apps/mooc_backend/features/step_definitions/evenBus.steps.ts
Original file line number Diff line number Diff line change
@@ -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]);
});