Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: request module #53

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open

feat: request module #53

wants to merge 6 commits into from

Conversation

eddort
Copy link
Member

@eddort eddort commented Oct 21, 2022

Middlewares - is the main difference between the request module and the fetch module

You can add global middlewares into the Module instance. These middlewares will be used for each request.
For example, this standard middlewares will be repeated on each network error 5 times and change the base URL on each failure.

Let's set up our module

import { Module } from '@nestjs/common';
import {
  RequestModule,
  repeat,
  rotate,
  notOkError,
} from '@lido-nestjs/request';
import { ConfigModule, ConfigService } from './my.service';

@Module({
  imports: [
    ConfigModule,
    RequestModule.forRootAsync({
      async useFactory(configService: ConfigService) {
        return {
          middlewares: [repeat(5), rotate(configService.baseUrls), notOkError],
        };
      },
      inject: [ConfigService],
    }),
  ],
})
export class MyModule {}

And use it

import { RequestService } from '@lido-nestjs/request';

export class MyService {
  constructor(private requestService: RequestService) {}

  async myFetch() {
    return await this.requestService.json({ url: '/url' });
  }
}

instance = createH(registryInstance);
});
test('based', async () => {
nock('http://yolo').get('/foo').reply(200, 'ok');
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm worried that we are generating dependencies in different packages. Can we reuse packages that are already in use in the lib?

Copy link
Member Author

Choose a reason for hiding this comment

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

You don't want to use this library?

},
) => {
results.map((result) => {
// @ts-ignore
Copy link
Contributor

Choose a reason for hiding this comment

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

could we use any instead?

Comment on lines +8 to +13
let response!: Response;
logger.log('Start request', config);
// eslint-disable-next-line prefer-const
response = await next(config);
logger.log('End request', config);
return response;
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
let response!: Response;
logger.log('Start request', config);
// eslint-disable-next-line prefer-const
response = await next(config);
logger.log('End request', config);
return response;
logger.log('Start request', config);
const response = await next(config);
logger.log('End request', config);
return response;

return {
result: error ? 'error' : 'success',
status: error ? status : response.status,
url: url.toString(),
Copy link
Contributor

Choose a reason for hiding this comment

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

we cannot store the entire url in the label:

  • with a large number of unique urls the prom database will get huge
  • the url may contain API keys

I think it's better to store only a domain name

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok! I will fix it

timer(serialize(config, response));
} catch (error) {
if (!(error instanceof Error)) {
timer(serialize(config, response));
Copy link
Contributor

Choose a reason for hiding this comment

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

Since you are not passing an error in args it will have success status in the metrics

in defaultSerializer:

result: error ? 'error' : 'success'

if (obj instanceof Object) {
return Object.keys(obj).reduce((newObj, key) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
Copy link
Contributor

Choose a reason for hiding this comment

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

pls, don't use @ts-ignore

@@ -0,0 +1,26 @@
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const deepClone = (obj: any) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it's better to move this to packages/utils

}

if (obj instanceof Array) {
return obj.reduce((arr, item, i) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

why reduce and not map? did you copy deepClone from some package?

Copy link
Contributor

Choose a reason for hiding this comment

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

perhaps we should take the cloneDeep from lodash?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, I do. I took it only for prototyping.

return (requestConfig: RequestConfig) => {
// copy object bcs we can mutate it
const internalConfig = deepClone(requestConfig) as InternalConfig;
internalConfig.attempt = 0;
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess that in service with a middleware design we should not have this configuration on a global level, but have it in the configs of the specific middleware responsible for repeating queries

Copy link
Member Author

Choose a reason for hiding this comment

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

What do you mean? Can I pass this variable to the context scope?

const fetchCall = ({ url, baseUrl, ...rest }: RequestConfig) =>
fetch(getUrl(baseUrl, url), rest);

export function compose(middleware: Middleware[]) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this module is written in more of a functional style and not very similar to the architecture of nestjs modules. It would be great if we could keep to one code style

Copy link
Member Author

Choose a reason for hiding this comment

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

I think a better way to create this module without some framework abstraction and wrote Nestjs binding. Because we don't need to use Nestjs abstraction inside this module. This way provides more clear code and it is a very popular way described in Uncle Bob's book.
And I think we should use this way in other modules in this repo. For example, middleware service looks very difficult but if we remove Nestjs abstraction from middleware code all ideas of that plugin will be looks like one line of code.

const response = await next(config);
if (!baseUrls.length) return response;
if (isNaN(config.attempt) || config.attempt <= 0) return response;
config.baseUrl = pickUrl(baseUrls, config.attempt);
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think it's good that we're mutating the config object here. If a mutation is required, I would prefer to have a context arg here, which contains the config

Copy link
Member Author

Choose a reason for hiding this comment

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

Okay! I will change the config and rewrite this case and all the same cases

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