-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
174 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export * from './query'; | ||
export * from './query-bus.interface'; | ||
export * from './local-query-bus'; | ||
121 changes: 121 additions & 0 deletions
121
packages/ddd-toolkit/src/query-bus/local-query-bus.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
import { LocalQueryBus } from './local-query-bus'; | ||
import { Query } from './query'; | ||
import { loggerMock } from '../logger'; | ||
import { waitFor } from '../utils'; | ||
|
||
class FooQuery extends Query<{ foo: string }> { | ||
constructor(public readonly payload: { foo: string }) { | ||
super(payload); | ||
} | ||
} | ||
|
||
class BarQuery extends Query<{ foo: string }> { | ||
constructor(public readonly payload: { foo: string }) { | ||
super(payload); | ||
} | ||
} | ||
|
||
describe('LocalQueryBus', () => { | ||
describe('Given a query bus', () => { | ||
let queryBus: LocalQueryBus; | ||
|
||
beforeEach(() => { | ||
queryBus = new LocalQueryBus(loggerMock); | ||
}); | ||
|
||
afterEach(() => { | ||
jest.resetAllMocks(); | ||
}); | ||
|
||
describe('Given no registered handler to foo query', () => { | ||
describe('When execute a foo query', () => { | ||
it('should throw', async () => { | ||
const query = new FooQuery({ foo: 'bar' }); | ||
await expect(() => queryBus.execute(query)).rejects.toThrow( | ||
`No handler found for ${FooQuery.name}`, | ||
); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('Given one registered handler to foo query', () => { | ||
const handler1Mock = jest.fn(); | ||
|
||
class FooQueryHandler { | ||
async handle(query: FooQuery) { | ||
await handler1Mock(query); | ||
} | ||
} | ||
|
||
beforeEach(() => { | ||
queryBus.register(FooQuery, new FooQueryHandler()); | ||
}); | ||
|
||
describe('When execute a foo query', () => { | ||
it('Should call handler', async () => { | ||
const query = new FooQuery({ foo: 'bar' }); | ||
await queryBus.execute(query); | ||
|
||
await waitFor(() => expect(handler1Mock).toBeCalledWith(query)); | ||
}); | ||
}); | ||
|
||
describe('Given a handler registered for bar query', () => { | ||
const handler3Mock = jest.fn(); | ||
|
||
class BarQueryHandler { | ||
async handle(query: BarQuery) { | ||
await handler3Mock(query); | ||
} | ||
} | ||
|
||
beforeEach(() => { | ||
queryBus.register(BarQuery, new BarQueryHandler()); | ||
}); | ||
|
||
describe('When execute foo query', () => { | ||
it('Should call only foo query handler', async () => { | ||
const query = new FooQuery({ foo: 'bar' }); | ||
await queryBus.execute(query); | ||
|
||
await waitFor(() => expect(handler1Mock).toBeCalledWith(query)); | ||
expect(handler3Mock).not.toBeCalled(); | ||
}); | ||
}); | ||
|
||
describe('When send bar query', () => { | ||
it('Should call only bar query handler', async () => { | ||
const query = new BarQuery({ foo: 'bar' }); | ||
await queryBus.execute(query); | ||
|
||
expect(handler1Mock).not.toBeCalled(); | ||
expect(handler3Mock).toBeCalledWith(query); | ||
}); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('Given one registered handler which fails the execution', () => { | ||
const handlerMock = jest.fn(); | ||
|
||
class FooQueryHandler { | ||
async handle(query: FooQuery) { | ||
await handlerMock(query); | ||
} | ||
} | ||
|
||
beforeEach(() => { | ||
handlerMock.mockRejectedValueOnce(new Error('ko')); | ||
queryBus.register(FooQuery, new FooQueryHandler()); | ||
}); | ||
|
||
describe('When execute query', () => { | ||
const query = new FooQuery({ foo: 'bar' }); | ||
|
||
it('should throw', async () => { | ||
await expect(() => queryBus.execute(query)).rejects.toThrow('ko'); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { ILogger } from '../logger'; | ||
import { IQuery, IQueryBus, IQueryClass, IQueryHandler } from './query-bus.interface'; | ||
|
||
export class LocalQueryBus implements IQueryBus { | ||
private handlers: { [key: string]: IQueryHandler<IQuery<unknown, unknown>> } = {}; | ||
|
||
constructor(private logger: ILogger) {} | ||
|
||
public register<Q extends IQuery<unknown, unknown>>(query: IQueryClass<Q>, handler: IQueryHandler<Q>): void { | ||
if (this.handlers[query.name]) throw new Error(`Query ${query.name} is already registered`); | ||
this.handlers[query.name] = handler; | ||
this.logger.debug(`Query ${query.name} registered`); | ||
} | ||
|
||
public async execute<Q extends IQuery<unknown, unknown>>(query: Q): Promise<Q['_resultType']> { | ||
const handler = this.handlers[query.name] as IQueryHandler<Q>; | ||
if (!handler) throw new Error(`No handler found for ${query.name}`); | ||
return await handler.handle(query); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
export interface IQuery<TPayload, TResult = void> { | ||
name: string; | ||
payload: TPayload; | ||
_resultType: TResult; | ||
} | ||
|
||
export interface IQueryClass<Q extends IQuery<unknown, unknown>> { | ||
new (payload: unknown): Q; | ||
} | ||
|
||
export interface IQueryHandler<Q extends IQuery<unknown, unknown>> { | ||
handle: (query: Q) => Promise<void>; | ||
} | ||
|
||
export interface IQueryBus { | ||
register<Q extends IQuery<unknown, unknown>>(query: IQueryClass<Q>, handler: IQueryHandler<Q>): void; | ||
|
||
execute<Q extends IQuery<unknown, unknown>>(query: Q): Promise<Q['_resultType']>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { IQuery } from './query-bus.interface'; | ||
|
||
export abstract class Query<TPayload, TResult = void> implements IQuery<TPayload, TResult> { | ||
readonly name: string; | ||
readonly _resultType: TResult; | ||
|
||
protected constructor(public readonly payload: TPayload) { | ||
this.name = this.constructor.name; | ||
} | ||
} |