Skip to content

WIP: Implementation proposition#1

Open
mateusz-kolecki wants to merge 9 commits intosnapshotpl:masterfrom
mateusz-kolecki:implementation-proposition
Open

WIP: Implementation proposition#1
mateusz-kolecki wants to merge 9 commits intosnapshotpl:masterfrom
mateusz-kolecki:implementation-proposition

Conversation

@mateusz-kolecki
Copy link

@mateusz-kolecki mateusz-kolecki commented May 15, 2018

Hi!
I would like to help create Command Bus library for TypeScript. Any feedback will be appreciated.

@mateusz-kolecki mateusz-kolecki force-pushed the implementation-proposition branch from b6f8d9b to 44ca126 Compare May 15, 2018 21:56
}

export interface CommandBus {
handle(command: Command): Promise<any>
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

void

}

public async run(command: Command, next: NextMiddleware): Promise<any> {
const start = Date.now();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move before call next()

throw err;
} finally {
const time = Date.now() - start;
this.logger.log(this.level, 'Command %s time %dms', commandName, time);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you want to log command two times if error occured?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.


import { Command } from '../commandBus';

export type NextMiddleware = () => Promise<any>;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Next middleware should receive command as arg

Copy link
Author

@mateusz-kolecki mateusz-kolecki May 18, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm... this was something I was thinking about for a long time...

My conclusion was this: if next() callback would receive command as argument then for most of the time it would be the same command in all meddlewares across the chain invocation. In other words it would be the same command as this passed to run() method.
Now i pass command to next middleware in the background (see for execution chain reducer);

Ok, but maby middleware can change command? Maby middleware creator would create his own command instance and pass this new one to next()?
In my opinion sneaky...

What do You think about this?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, i added command as argument

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

const handler = this.handlers.get(command.constructor as ConstructorOf<Command>);

if (!handler) {
throw new Error(`Could not resolve handler for ${command.constructor.name}`);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imo this exception should come from get()

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain in more details?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

closed

return this.handlers.size;
}

public entries(): IterableIterator<[ConstructorOf<Command>, Handler<Command>]> {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we need this?

export class HandlerResolverInMemory implements HandlerResolver {
private handlers = new Map<ConstructorOf<Command>, Handler<Command>>();

public register<T>(commandConstructor: ConstructorOf<T>, handler: Handler<T>): HandlerResolverInMemory {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not to get this list by constructor?

Copy link
Author

@mateusz-kolecki mateusz-kolecki May 18, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to be sure that each entry in Map is a command constructor as key and handler instance which handles exacly this particular command as value.
Now it can be checked at compile time for register() method calls.
Have no idea, how to declare handlers property to get same result.
Now propert is declared as any command constructor as value and any handler instane which handles any command.

In other words each call to register() method is made with some generic type which is inferred from command constructor type and this allow compiler to check that handler instance matches that command type.

This prevent mistakes like registering handler for another command.

it("should write info logs", async () => {
await commander.handle(new HelloCommand('John', 'Doe'));

const infos = logger.messages.filter(entry => entry.level === 'info');
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we want to test logger middeware here?


export class FakeLogger implements CommandLogger {

public messages: { level: string, msg: string, meta: any }[] = [];
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if public, can be changed out of scope

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is also dump implementation for testing. I can use read-only getter like this:

class FakeLogger implements CommandLogger {
    // ...
    public get Messages() {
        return this.messages;
    }
}

and then in test code:

const infos = logger.Messages.filter(...);

What do you think about this?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

import { Middleware, NextMiddleware } from '../../src/middlewares';


class CallbackMiddleware implements Middleware {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

final

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can exist as provided implementation

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TypeScript do not offer final classes :(

And this class is only for testing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants