Skip to content

Commit a69946d

Browse files
ivanportilloaliondevrsaladocid
committed
Add query bus (WIP)
Co-authored-by: aliondev <antonioleon@audiense.com> Co-authored-by: Rubén Salado <rsaladocid@users.noreply.github.com>
1 parent a4605d2 commit a69946d

File tree

8 files changed

+105
-0
lines changed

8 files changed

+105
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export abstract class Query {}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { Query } from './Query';
2+
3+
export interface QueryBus {
4+
ask<T>(query: Query): Promise<T>;
5+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { Query } from './Query';
2+
import { Response } from './Response';
3+
4+
export interface QueryHandler<T extends Query, R extends Response> {
5+
subscribedTo(): Query;
6+
handle(query: T): Promise<R>;
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { Query } from './Query';
2+
3+
export class QueryNotRegisteredError extends Error {
4+
constructor(query: Query) {
5+
super(`The query <${query.constructor.name}> hasn't a query handler associated`);
6+
}
7+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export interface Response {}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { Query } from '../../domain/Query';
2+
import { QueryHandler } from '../../domain/QueryHandler';
3+
import { Response } from '../../domain/Response';
4+
import { QueryNotRegisteredError } from '../../domain/QueryNotRegisteredError';
5+
6+
export class QueryHandlersInformation {
7+
private queryHandlersMap: Map<Query, QueryHandler<Query, Response>>;
8+
9+
constructor(queryHandlers: Array<QueryHandler<Query, Response>>) {
10+
this.queryHandlersMap = this.formatHandlers(queryHandlers);
11+
}
12+
13+
private formatHandlers(queryHandlers: Array<QueryHandler<Query, Response>>): Map<Query, QueryHandler<Query, Response>> {
14+
const handlersMap = new Map();
15+
16+
queryHandlers.forEach(queryHandler => {
17+
handlersMap.set(queryHandler.subscribedTo(), queryHandler);
18+
});
19+
20+
return handlersMap;
21+
}
22+
23+
public search(query: Query): QueryHandler<Query, Response> {
24+
const queryHandler = this.queryHandlersMap.get(query.constructor);
25+
26+
if (!queryHandler) {
27+
throw new QueryNotRegisteredError(query);
28+
}
29+
30+
return queryHandler;
31+
}
32+
}

src/apps/mooc_backend/controllers/CoursesCounterGetController.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ export class CoursesCounterGetController implements Controller {
88
constructor(private coursesCounterFinder: CoursesCounterFinder) {}
99
async run(req: Request, res: Response): Promise<void> {
1010
try {
11+
const query = new FindCourseCounterQuery();
12+
const count = await this.queryBus.query(query);
13+
1114
const counter = await this.coursesCounterFinder.run();
1215
res.status(httpStatus.OK).send(counter);
1316
} catch (e) {
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { Query } from '../../../../../src/Contexts/Shared/domain/Query';
2+
import { QueryHandlersInformation } from '../../../../../src/Contexts/Shared/infrastructure/QueryBus/QueryHandlersInformation';
3+
import { QueryNotRegisteredError } from '../../../../../src/Contexts/Shared/domain/QueryNotRegisteredError';
4+
import { QueryHandler } from '../../../../../src/Contexts/Shared/domain/QueryHandler';
5+
import { Response } from '../../../../../src/Contexts/Shared/domain/Response';
6+
7+
class UnhandledQuery extends Query {
8+
static QUERY_NAME = 'unhandled.query';
9+
}
10+
11+
class HandledQuery extends Query {
12+
static QUERY_NAME = 'handled.query';
13+
}
14+
15+
class MyQueryHandler implements QueryHandler<Query, Response> {
16+
subscribedTo(): HandledQuery {
17+
return HandledQuery;
18+
}
19+
20+
async handle(query: HandledQuery): Promise<Response> {return {};}
21+
}
22+
23+
describe('InMemoryQueryBus', () => {
24+
it('throws an error if dispatches a query without handler', async () => {
25+
const unhandledQuery = new UnhandledQuery();
26+
const queryHandlersInformation = new QueryHandlersInformation([]);
27+
const queryBus = new InMemoryQueryBus(queryHandlersInformation);
28+
29+
let exception = null;
30+
31+
try {
32+
await queryBus.ask(unhandledQuery);
33+
} catch (error) {
34+
exception = error;
35+
}
36+
37+
expect(exception).toBeInstanceOf(QueryNotRegisteredError);
38+
expect(exception.message).toBe(`The query <UnhandledCommand> hasn't a query handler associated`);
39+
});
40+
41+
it('accepts a query with handler', async () => {
42+
const handledQuery = new HandledQuery();
43+
const myQueryHandler = new MyQueryHandler();
44+
const queryHandlersInformation = new QueryHandlersInformation([myQueryHandler]);
45+
const queryBus = new InMemoryQueryBus(queryHandlersInformation);
46+
47+
await queryBus.ask(handledQuery);
48+
});
49+
});

0 commit comments

Comments
 (0)