Skip to content

Commit

Permalink
feat(bus): implement HTTP command dispatcher (#20)
Browse files Browse the repository at this point in the history
closes #19
  • Loading branch information
RomanReznichenko authored Apr 13, 2022
1 parent e52ced2 commit ec3b30d
Show file tree
Hide file tree
Showing 16 changed files with 616 additions and 5 deletions.
113 changes: 112 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@
"dependencies": {
"amqp-connection-manager": "^4.1.1",
"amqplib": "^0.8.0",
"axios": "^0.26.1",
"axios-rate-limit": "^1.3.0",
"reflect-metadata": "^0.1.13",
"tslib": "~2.3.1",
"tsyringe": "^4.6.0",
Expand Down Expand Up @@ -110,6 +112,7 @@
"is-ci": "~3.0.1",
"jest": "^27.5.1",
"lint-staged": "^12.3.4",
"nock": "^13.2.4",
"nx": "13.9.4",
"prettier": "2.6.0",
"semantic-release": "~19.0.2",
Expand Down
84 changes: 84 additions & 0 deletions packages/bus/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,90 @@ await bus.execute(command);

For more information, please see `@secbox/core`.

### HttpComandDispatcher

The `HttpComandDispatcher` is an alternative way to execute RPC command. By default you can use `AxiosCommandDispatcher` to execute command.

```ts
import { AxiosCommandDispatcher, AxiosCommandDispatcherConfig } from '@secbox/bus';

const options: AxiosCommandDispatcherConfig = {
baseUrl,
token: 'weobbz5.nexa.vennegtzr2h7urpxgtksetz2kwppdgj0'
};

const axiosDispatcher = new AxiosCommandDispatcher(options);
```
The `AxiosCommandDispatcherConfig` implementation exposes the properties described below:

| Option | Description |
|-----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `baseUrl` | Application url |
| `token` | Authorization token |
| `timeout` | If the request takes longer than `timeout`, the request will be aborted. Default 10000 |
| `rate` | Contain two options: `perMilliseconds` - amount of time to limit concurrent requests; `maxRequests` - max requests to perform concurrently in given amount of time. |

#### Custom HttpComandDispatcher

You can implement your own `HttpComandDispatcher`. To do it you should implement`HttpComandDispatcher` interface.

```ts
import { Command } from '@secbox/core';
import { HttpComandDispatcher } from '@secbox/bus';

export class CustomHttpDispather implements HttpComandDispatcher {
constuctor(/*options*/) {
// ...
}

publick execute<T, R>(command: Command<T, R>): Promise<R> {
// your implementation
}
}
```

#### Executing RPC methods

To execute command `HttpComandDispatcher` exposes a `execute()` method. This method is intended to perform a command to the application and returns an `Promise` with its response.

```ts
interface Payload {
version: string;
}

interface Response {
lastVersion: string;
}

const options: HttpOptions<Payload> = {
payload: { version: '0.0.1' },
url: '/api/test',
method: 'GET'
};

const command = new HttpCommand<Payload, Response>(options);

const response = await axiosDispatcher.execute(command);
```
This method returns a Promise which will eventually be resolved as a response message.

As you can see in example below in case when you use `HttpCommandDispatcher` to execute command you should use `HttpCommand<T, R>`. To ginfigure your command you should pass `HttpOptions<T>` to `HttpCommand<T, R>` constructor.

The `HttpOptions<T>` implementation exposes the properties described below:

| Option | Description |
|-----------------|--------------------------------------------------------------------------------------------|
| `url` | Application URL address |
| `payload` | Message that we want to transmit to the remote service. |
| `method` | HTTP method |
| `expectReply` | ndicates whether to wait for a reply. By default true. |
| `ttl` | Period of time that command should be handled before being discarded. By default 10000 ms. |
| `type` | The name of a command. By default, it is the name of specific class. |
| `correlationId` | Used to ensure atomicity while working with EventBus. By default, random UUID. |
| `params` | Query parameters |
| `createdAt` | The exact date and time the command was created. |


#### Retry Strategy

For some noncritical operations, it is better to fail as soon as possible rather than retry a coupe of times.
Expand Down
2 changes: 1 addition & 1 deletion packages/bus/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,6 @@
"bus"
],
"peerDependencies": {
"@secbox/core": "^0.3.0"
"@secbox/core": ">=0.3.0"
}
}
2 changes: 0 additions & 2 deletions packages/bus/src/brokers/index.ts

This file was deleted.

63 changes: 63 additions & 0 deletions packages/bus/src/commands/HttpCommand.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { HttpCommand, HttpOptions } from './HttpCommand';
import { Method } from 'axios';

describe('HttpCommand', () => {
describe('constructor', () => {
it('should set default values to props', () => {
// arrange
const options: HttpOptions<string> = {
payload: 'Test',
url: '/api/test',
method: 'GET'
};

// act
const command = new HttpCommand(options);

// assert
expect(command).toMatchObject({
ttl: 10000,
expectReply: true,
type: 'HttpCommand',
createdAt: expect.any(Date),
correlationId: expect.any(String)
});
});

it('should set GET to method by default', () => {
// arrange
const options: HttpOptions<string> = {
payload: 'Test',
url: '/api/test'
};

// act
const command = new HttpCommand(options);

// assert
expect(command).toMatchObject({
method: 'GET'
});
});

it('should raise an exception if method is not string', () => {
// arrange
const options = {
payload: 'Test',
url: '/api/test',
method: 0 as unknown as Method
};

// act / assert
expect(() => new HttpCommand(options)).toThrow('`method` must be string');
});

it('should raise an exception if url is not string', () => {
// arrange
const options = { payload: 'Test', url: 0 as unknown as string };

// assert
expect(() => new HttpCommand(options)).toThrow('`url` must be string');
});
});
});
Loading

0 comments on commit ec3b30d

Please sign in to comment.