Skip to content

Commit 7d436c5

Browse files
authored
Backoffice projection (#80)
* Add save method to BackofficeCourseRepository * Create BackofficeCourseCreator * Create backoffice course on course created * Fix test
1 parent 2790a8a commit 7d436c5

File tree

14 files changed

+177
-29
lines changed

14 files changed

+177
-29
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { BackofficeCourse } from '../../domain/BackofficeCourse';
2+
import { BackofficeCourseDuration } from '../../domain/BackofficeCourseDuration';
3+
import { BackofficeCourseId } from '../../domain/BackofficeCourseId';
4+
import { BackofficeCourseName } from '../../domain/BackofficeCourseName';
5+
import { BackofficeCourseRepository } from '../../domain/BackofficeCourseRepository';
6+
7+
export class BackofficeCourseCreator {
8+
constructor(private backofficeCourseRepository: BackofficeCourseRepository) {}
9+
10+
async run(id: string, duration: string, name: string) {
11+
const course = new BackofficeCourse(
12+
new BackofficeCourseId(id),
13+
new BackofficeCourseName(name),
14+
new BackofficeCourseDuration(duration)
15+
);
16+
17+
return this.backofficeCourseRepository.save(course);
18+
}
19+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { CourseCreatedDomainEvent } from '../../../Mooc/Courses/domain/CourseCreatedDomainEvent';
2+
import { DomainEventClass } from '../../../Shared/domain/DomainEvent';
3+
import { DomainEventSubscriber } from '../../../Shared/domain/DomainEventSubscriber';
4+
import { BackofficeCourseCreator } from './BackofficeCourseCreator';
5+
6+
export class CreateBackofficeCourseOnCourseCreated implements DomainEventSubscriber<CourseCreatedDomainEvent> {
7+
constructor(private creator: BackofficeCourseCreator) {}
8+
9+
subscribedTo(): DomainEventClass[] {
10+
return [CourseCreatedDomainEvent];
11+
}
12+
13+
async on(domainEvent: CourseCreatedDomainEvent): Promise<void> {
14+
const { aggregateId, duration, name } = domainEvent;
15+
16+
return this.creator.run(aggregateId, duration, name);
17+
}
18+
}

src/Contexts/Backoffice/domain/BackofficeCourseRepository.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ import { BackofficeCourse } from './BackofficeCourse';
22

33
export interface BackofficeCourseRepository {
44
searchAll(): Promise<Array<BackofficeCourse>>;
5+
save(course: BackofficeCourse): Promise<void>;
56
}

src/apps/backoffice/backend/app.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import express from 'express';
33
import helmet from 'helmet';
44
import compress from 'compression';
55
import { registerRoutes } from './routes';
6+
import { registerSubscribers } from './subscribers';
67

78
const app: express.Express = express();
89

@@ -17,5 +18,6 @@ app.use(helmet.frameguard({ action: 'deny' }));
1718
app.use(compress());
1819

1920
registerRoutes(app);
21+
registerSubscribers();
2022

2123
export default app;

src/apps/backoffice/backend/config/dependency-injection/Courses/application.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,13 @@ services:
1313
tags:
1414
- { name: 'queryHandler' }
1515

16+
Backoffice.Backend.courses.BackofficeCourseCreator:
17+
class: ../../../../../../Contexts/Backoffice/application/Courses/BackofficeCourseCreator
18+
arguments: ["@Backoffice.Backend.courses.BackofficeCourseRepository"]
19+
20+
Backoffice.Backend.courses.CreateBackofficeCourseOnCourseCreated:
21+
class: ../../../../../../Contexts/Backoffice/application/Courses/CreateBackofficeCourseOnCourseCreated
22+
arguments: ['@Backoffice.Backend.courses.BackofficeCourseCreator']
23+
tags:
24+
- { name: 'domainEventSubscriber' }
25+

src/apps/backoffice/backend/config/dependency-injection/Shared/application.yaml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,17 @@ services:
1515

1616
Shared.QueryBus:
1717
class: ../../../../../../Contexts/Shared/infrastructure/QueryBus/InMemoryQueryBus
18-
arguments: ['@Shared.QueryHandlersInformation']
18+
arguments: ['@Shared.QueryHandlersInformation']
19+
20+
Shared.EventBus:
21+
class: ../../../../../../Contexts/Shared/infrastructure/EventBus/InMemorySyncEventBus
22+
arguments: []
23+
24+
Shared.EventBus.DomainEventMapping:
25+
class: ../../../../../../Contexts/Shared/infrastructure/EventBus/DomainEventMapping
26+
arguments: ['!tagged domainEventSubscriber']
27+
28+
Shared.EventBus.DomainEventJsonDeserializer:
29+
class: ../../../../../../Contexts/Shared/infrastructure/EventBus/DomainEventJsonDeserializer
30+
arguments: ['@Shared.EventBus.DomainEventMapping']
31+
Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { Request, Response } from 'express';
22
import httpStatus from 'http-status';
33
import { SearchAllCoursesQuery } from '../../../../Contexts/Backoffice/application/SearchAll/SearchAllCoursesQuery';
4+
import { SearchAllCoursesResponse } from '../../../../Contexts/Backoffice/application/SearchAll/SearchAllCoursesResponse';
5+
import { BackofficeCourse } from '../../../../Contexts/Backoffice/domain/BackofficeCourse';
46
import { QueryBus } from '../../../../Contexts/Shared/domain/QueryBus';
57
import { Controller } from './Controller';
68

@@ -9,8 +11,15 @@ export class CoursesGetController implements Controller {
911

1012
async run(_req: Request, res: Response) {
1113
const query = new SearchAllCoursesQuery();
12-
const courses = await this.queryBus.ask(query);
14+
const queryResponse: SearchAllCoursesResponse = await this.queryBus.ask(query);
15+
res.status(httpStatus.OK).send(this.toResponse(queryResponse.courses));
16+
}
1317

14-
res.status(httpStatus.OK).send(courses);
18+
private toResponse(courses: Array<BackofficeCourse>) {
19+
return courses.map(course => ({
20+
id: course.id.toString(),
21+
duration: course.duration.toString(),
22+
name: course.name.toString()
23+
}));
1524
}
1625
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import container from './config/dependency-injection';
2+
import { InMemoryAsyncEventBus } from '../../../Contexts/Shared/infrastructure/EventBus/InMemoryAsyncEventBus';
3+
import { Definition } from 'node-dependency-injection';
4+
import { DomainEventSubscriber } from '../../../Contexts/Shared/domain/DomainEventSubscriber';
5+
import { DomainEvent } from '../../../Contexts/Shared/domain/DomainEvent';
6+
7+
export function registerSubscribers() {
8+
const eventBus = container.get('Shared.EventBus') as InMemoryAsyncEventBus;
9+
const subscriberDefinitions = container.findTaggedServiceIds('domainEventSubscriber') as Map<String, Definition>;
10+
const subscribers: Array<DomainEventSubscriber<DomainEvent>> = [];
11+
12+
subscriberDefinitions.forEach((value: any, key: any) => subscribers.push(container.get(key)));
13+
eventBus.addSubscribers(subscribers);
14+
}

tests/Contexts/Backoffice/__mocks__/BackofficeCourseRepositoryMock.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { BackofficeCourseRepository } from '../../../../src/Contexts/Backoffice/
33

44
export class BackofficeCourseRepositoryMock implements BackofficeCourseRepository {
55
private mockSearchAll = jest.fn();
6+
private mockSave = jest.fn();
67
private courses: Array<BackofficeCourse> = [];
78

89
returnOnSearchAll(courses: Array<BackofficeCourse>) {
@@ -17,4 +18,12 @@ export class BackofficeCourseRepositoryMock implements BackofficeCourseRepositor
1718
assertSearchAll() {
1819
expect(this.mockSearchAll).toHaveBeenCalled();
1920
}
21+
22+
async save(course: BackofficeCourse): Promise<void> {
23+
this.mockSave(course);
24+
}
25+
26+
assertSaveHasBeenCalledWith(course: BackofficeCourse) {
27+
expect(this.mockSave).toHaveBeenCalledWith(course);
28+
}
2029
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import faker from 'faker';
2+
import { BackofficeCourseCreator } from '../../../../../src/Contexts/Backoffice/application/Courses/BackofficeCourseCreator';
3+
import { BackofficeCourseRepositoryMock } from '../../__mocks__/BackofficeCourseRepositoryMock';
4+
import { BackofficeCourseMother } from '../domain/BackofficeCourseMother';
5+
describe('BackofficeCourseCreator', () => {
6+
it('creates a backoffice course', async () => {
7+
const course = BackofficeCourseMother.random();
8+
9+
const repository = new BackofficeCourseRepositoryMock();
10+
const applicationService = new BackofficeCourseCreator(repository);
11+
12+
await applicationService.run(course.id.toString(), course.duration.toString(), course.name.toString());
13+
14+
repository.assertSaveHasBeenCalledWith(course);
15+
});
16+
});

0 commit comments

Comments
 (0)