Skip to content

Commit

Permalink
feat: cqrs rework (#72)
Browse files Browse the repository at this point in the history
  • Loading branch information
Mararok authored Sep 23, 2024
1 parent 370f4ae commit 3b6d1ac
Show file tree
Hide file tree
Showing 12 changed files with 168 additions and 117 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@
"prepublish": "yarn run build"
},
"peerDependencies": {
"@hexancore/common": "^0.15.0",
"@hexancore/common": "^0.16.0",
"@nestjs/common": "^10.3.9",
"@nestjs/config": "^3.0.0",
"@nestjs/core": "^10.3.9",
Expand Down Expand Up @@ -103,7 +103,7 @@
"tslib": "^2.6.3"
},
"devDependencies": {
"@hexancore/common": "^0.15.0",
"@hexancore/common": "^0.16.0",
"@hexancore/mocker": "^1.1.2",
"@nestjs/cli": "^10.3.2",
"@nestjs/common": "^10.3.9",
Expand Down
19 changes: 9 additions & 10 deletions src/Application/DefaultGeneralBus.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { AR, ARW, OKA, AnyHCommand, AnyHQuery, AnyHEvent } from '@hexancore/common';
import { Injectable } from '@nestjs/common';
import { CommandBus, EventBus, ICommand, IEvent, IQuery, QueryBus } from '@nestjs/cqrs';
import { ARW, AsyncResult, OKA } from '@hexancore/common';
import { GeneralBus } from './GeneralBus';
import { CommandBus, EventBus, QueryBus } from '@nestjs/cqrs';
import { GeneralBus, type HCommandHandleResult, type HQueryHandleResult } from './GeneralBus';

@Injectable()
export class DefaultGeneralBus extends GeneralBus {
Expand All @@ -16,16 +16,15 @@ export class DefaultGeneralBus extends GeneralBus {
this.queryBus = queryBus;
}

public handleCommand<T>(command: ICommand): AsyncResult<T> {
return ARW(this.commandBus.execute(command));
public handleCommand<T extends AnyHCommand>(command: T): HCommandHandleResult<T> {
return ARW(this.commandBus.execute(command)) as any;
}

public handleEvent(event: IEvent): AsyncResult<boolean> {
this.eventBus.publish(event);
return OKA(true);
public handleEvent(event: AnyHEvent): AR<boolean> {
return ARW(this.eventBus.publish(event)).mapToTrue();
}

public handleQuery<T>(query: IQuery): AsyncResult<T> {
return ARW(this.queryBus.execute(query));
public handleQuery<T extends AnyHQuery>(query: T): HQueryHandleResult<T> {
return ARW(this.queryBus.execute(query)) as any;
}
}
22 changes: 15 additions & 7 deletions src/Application/GeneralBus.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import { AsyncResult } from '@hexancore/common';
import { Injectable } from '@nestjs/common';
import { ICommand, IEvent, IQuery } from '@nestjs/cqrs';
import {
AR,
AnyHCommand,
ExtractHCommandResultValueType,
AnyHQuery,
ExtractHQueryResultValueType,
AnyHEvent
} from '@hexancore/common';


export type HCommandHandleResult<T extends AnyHCommand> = AR<ExtractHCommandResultValueType<T>>;
export type HQueryHandleResult<T extends AnyHQuery> = AR<ExtractHQueryResultValueType<T>>;

@Injectable()
export abstract class GeneralBus {
public abstract handleCommand<T>(command: ICommand): AsyncResult<T>;
public abstract handleEvent(event: IEvent): AsyncResult<boolean>;
public abstract handleQuery<T>(query: IQuery): AsyncResult<T>;
public abstract handleCommand<T extends AnyHCommand>(command: T): HCommandHandleResult<T>;
public abstract handleEvent(event: AnyHEvent): AR<boolean>;
public abstract handleQuery<T extends AnyHQuery>(query: T): HQueryHandleResult<T>;
}
13 changes: 6 additions & 7 deletions src/Infrastructure/Http/Controller/AbstractAppController.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
import { GeneralBus } from '../../../Application/GeneralBus';
import { ICommand, IQuery } from '@nestjs/cqrs';
import { AnyHCommand, AnyHQuery } from '@hexancore/common';
import { GeneralBus, type HCommandHandleResult, type HQueryHandleResult } from '../../../Application';
import { Inject } from '@nestjs/common';
import { type AR } from '@hexancore/common';
import { AbstractController } from './AbstractController';

export abstract class AbstractAppController extends AbstractController {
public constructor(@Inject() private gb: GeneralBus) {
super();
}

protected handleCommand<C extends ICommand, R>(command: C): AR<R> {
return this.gb.handleCommand<R>(command);
protected handleCommand<T extends AnyHCommand>(command: T): HCommandHandleResult<T> {
return this.gb.handleCommand(command);
}

protected handleQuery<Q extends IQuery, R>(query: Q): AR<R> {
return this.gb.handleQuery<R>(query);
protected handleQuery<T extends AnyHQuery>(query: T): HQueryHandleResult<T> {
return this.gb.handleQuery(query);
}
}
25 changes: 13 additions & 12 deletions src/Test/Application/MockGeneralBus.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { AsyncResult, OKA } from '@hexancore/common';
import { AR, OKA, AnyHCommand, AnyHQuery, AnyHEvent } from '@hexancore/common';
import { Injectable } from '@nestjs/common';
import { ICommand, IEvent, IQuery } from '@nestjs/cqrs';
import { IEvent } from '@nestjs/cqrs';
import deepEqual from 'deep-equal';
import type { HCommandHandleResult, HQueryHandleResult } from "../../Application";
import { GeneralBus } from '../../Application/GeneralBus';

interface HandleExpectation {
message: ICommand | IQuery | IEvent;
result: AsyncResult<any>;
message: AnyHCommand | AnyHQuery;
result: AR<any>;
}

@Injectable()
Expand All @@ -22,19 +23,19 @@ export class MockGeneralBus extends GeneralBus {
this.queriesToHandle = [];
}

public expectHandleCommand(command: ICommand, result: AsyncResult<any>): void {
this.commandsToHandle.unshift({ message: command, result });
public expectHandleCommand<T extends AnyHCommand>(command: T, result: HCommandHandleResult<T>): void {
this.commandsToHandle.unshift({ message: command as any, result });
}

public expectHandleEvent(event: IEvent | ((event: IEvent) => boolean)): void {
public expectHandleEvent(event: AnyHEvent | ((event: AnyHEvent) => boolean)): void {
this.eventsToHandle.unshift(event);
}

public expectHandleQuery(query: IQuery, result: AsyncResult<any>): void {
this.queriesToHandle.unshift({ message: query, result });
public expectHandleQuery<T extends AnyHQuery>(query: T, result: HQueryHandleResult<T>): void {
this.queriesToHandle.unshift({ message: query as any, result });
}

public handleCommand(command: ICommand): AsyncResult<any> {
public handleCommand<T extends AnyHCommand>(command: T): HCommandHandleResult<T> {
const expectation = this.commandsToHandle.pop();
if (expectation && deepEqual(expectation.message, command)) {
return expectation.result;
Expand All @@ -43,7 +44,7 @@ export class MockGeneralBus extends GeneralBus {
throw new Error('Unexpected command to handle: ' + command.constructor.name + ' ' + JSON.stringify(command, null, 1));
}

public handleEvent(event: IEvent): AsyncResult<boolean> {
public handleEvent(event: AnyHEvent): AR<boolean> {
const expectation = this.eventsToHandle.pop();
if (typeof expectation == 'function') {
if (!expectation(event)) {
Expand All @@ -56,7 +57,7 @@ export class MockGeneralBus extends GeneralBus {
return OKA(true);
}

public handleQuery(query: IQuery): AsyncResult<any> {
public handleQuery<T extends AnyHQuery>(query: T): HQueryHandleResult<T> {
const expectation = this.queriesToHandle.pop();
if (expectation && deepEqual(expectation.message, query)) {
return expectation.result;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export class BookCreateCommand {
public constructor(public readonly title: string) {}
import { HCommand } from "@hexancore/common";

export class BookCreateCommand extends HCommand<BookCreateCommand, void> {
public title!: string;
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
export class BookGetByIdQuery {
public constructor(public readonly title: string) {}
import { HQuery } from "@hexancore/common";
import type { BookDto } from "../../Dto/BookDto";

export class BookGetByIdQuery extends HQuery<BookGetByIdQuery, BookDto> {
public title!: string;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { UIntValue, ValueObject } from "@hexancore/common";
import { UIntValue } from "@hexancore/common";

@ValueObject('Book')
export class BookCopyId extends UIntValue<BookCopyId> { }
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { UIntValue, ValueObject } from "@hexancore/common";
import { UIntValue } from "@hexancore/common";

@ValueObject('Book')
export class BookId extends UIntValue<BookId> { }
79 changes: 55 additions & 24 deletions test/unit/Application/DefaultGeneralBus.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,45 @@
* @group unit/core
*/

import { CommandBus, EventBus, ICommand, IEvent, IQuery, QueryBus } from '@nestjs/cqrs';
import { CommandBus, EventBus, QueryBus } from '@nestjs/cqrs';
import { Mocker } from '@hexancore/mocker';
import { Email, ERR, ERRA, OK, OKA } from '@hexancore/common';
import { Email, ERR, ERRA, HCommand, HEvent, HQuery, INTERNAL_ERR, OK, OKA, type JsonObjectType } from '@hexancore/common';
import { GeneralBus } from '@/Application/GeneralBus';
import { DefaultGeneralBus } from '@/Application/DefaultGeneralBus';

class TestCommand implements ICommand {
public readonly email: Email;
public constructor(emailRaw: string) {
this.email = Email.c(emailRaw).v;
class TestCommand extends HCommand<TestCommand, boolean> {
public constructor(public readonly email: Email) {
super();
}

public toJSON(): JsonObjectType<TestCommand> {
return {
email: this.email.toJSON(),
};
}
}

class TestEvent implements IEvent {
public readonly email: Email;
public constructor(emailRaw: string) {
this.email = Email.c(emailRaw).v;
class TestEvent extends HEvent<TestEvent> {
public constructor(public readonly email: Email) {
super();
}

public toJSON(): JsonObjectType<TestEvent> {
return {
email: this.email.toJSON(),
};
}
}

class TestQuery implements IQuery {
public readonly email: Email;
public constructor(emailRaw: string) {
this.email = Email.c(emailRaw).v;
class TestQuery extends HQuery<TestQuery, boolean> {
public constructor(public readonly email: Email) {
super();
}

public toJSON(): JsonObjectType<TestQuery> {
return {
email: this.email.toJSON(),
};
}
}

Expand All @@ -50,35 +65,52 @@ describe('DefaultGeneralBus', () => {
});

describe('handleCommand', () => {
const command = new TestCommand('[email protected]');
const command = new TestCommand(Email.cs('[email protected]'));

test('when ok', async () => {
const expectedResult = OKA(true);

commandBus.expects('execute', command).andReturn(expectedResult.p);

const currentResult = await gb.handleCommand(command);
const current = await gb.handleCommand(command);

expect(currentResult).toEqual(OK(true));
expect(current).toEqual(OK(true));
});

test('when error', async () => {
const expectedResult = ERRA({ type: 'test' });

commandBus.expects('execute', command).andReturn(expectedResult.p);

const currentResult = await gb.handleCommand(command);
const current = await gb.handleCommand(command);

expect(currentResult).toEqual(ERR({ type: 'test' }));
expect(current).toEqual(ERR({ type: 'test' }));
});
});

describe('handleEvent', () => {
const event: TestEvent = new TestEvent(Email.cs('[email protected]'));

test('when ok', async () => {
eventBus.expects('publish', event).andReturn(Promise.resolve());

const current = await gb.handleEvent(event);

expect(current).toEqual(OK(true));
});

test('when error', async () => {
eventBus.expects('publish', event).andReturnWith(async () => { throw new Error("test"); });

const current = await gb.handleEvent(event);

expect(current).toEqual(INTERNAL_ERR(new Error("test")));
});
});

describe('handleQuery', () => {
const query = new TestQuery('[email protected]');
const query = new TestQuery(Email.cs('[email protected]'));

test('when ok', async () => {
const expectedResult = OKA(true);

queryBus.expects('execute', query).andReturn(expectedResult.p);

const currentResult = await gb.handleQuery(query);
Expand All @@ -88,7 +120,6 @@ describe('DefaultGeneralBus', () => {

test('when error', async () => {
const expectedResult = ERRA({ type: 'test' });

queryBus.expects('execute', query).andReturn(expectedResult.p);

const currentResult = await gb.handleQuery(query);
Expand Down
Loading

0 comments on commit 3b6d1ac

Please sign in to comment.