DEV Community

Cover image for Advanced NestJS: Dynamic Providers

Advanced NestJS: Dynamic Providers

Livio Brunner on August 17, 2019

Livio is a member of the NestJS core team and creator of the @nestjs/terminus integration Intro Dependency Injection (short DI) is a po...
Collapse
 
bhaidar profile image
Bilal Haidar

Hi Livio,
Can you please illustrate more on this:
So how do we bypass that? The holy words are Dynamic Module

How would that overcome the problem? Are classes' constructors called before calling a Dynamic Module?

Thanks

Collapse
 
dannyhuly profile image
Daniel Huly

Great article. I was looking for this kind of example.

I had only one issue with re-exporting the dynamic LoggerModule.
The prefixesForLoggers was not call in time (same issues as you explained in the article) due to other modules loading.

In my case I have a CoreModule that includes many base modules and services.

I was able to overcome the issue by making the LoggerModule an Asyc module (using Promise and setTimeout(..., 0)). This let all the files and @Logger(...) to be called and add all the prefixes to prefixesForLoggers array before resolving.

// logger.module.ts

export class LoggerModule {
  static forRoot(): Promise<DynamicModule> {

    return new Promise(resolve => {
      setTimeout(() => {
        const prefixedLoggerProviders = createLoggerProviders();
        resolve({
          module: LoggerModule,
          providers: [LoggerService, ...prefixedLoggerProviders],
          exports: [LoggerService, ...prefixedLoggerProviders],
        })
      }, 0);
    })

  }
}
Enter fullscreen mode Exit fullscreen mode

Thanks :)

Collapse
 
ehaynes99 profile image
Eric Haynes

Or, you could just...

class AppService {
  private logger = LoggerService.withPrefix('AppService');
  // ...
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
brunnerlivio profile image
Livio Brunner • Edited

That is why the article is called "Advanced NestJS: Dynamic Providers", not "How to create your own Logging Library".

Seems like I failed to convey that "Logger" was just an easy-to-understand vehicle to bring closer how you can dynamically generate providers using NestJS. This is quite powerful pattern which is used for instance in @nestjs/typeorm.

Your average user won't need this. This is mainly targeted for advanced NestJS users (hence the title) + library authors

In heavens sake I don't wanna say "You should build your own logger like this". Off course not, that is why we already provide the logger you mentioned .

I am part of the NestJS core team -- off course I wouldn't try to promote a different logger. That wasn't my point of the article. Sorry for the misunderstanding will try to make it clearer next time.

Collapse
 
ehaynes99 profile image
Eric Haynes

Respectfully, that it was a logger wasn't relevant to my comment either. I would feel the same about it as a queue name, or a repository type. Rather, I was pointing out that this is quite a lot of complexity to solve the "problem" of passing a string to a function.

I don't get this obsession with decorators. They're not a particulary powerful pattern. You shed the runtime context that's necessary to instantiate things, and it takes a tremendous amount of effort to avoid falling back on static contexts, paradoxically creating the problem that DI frameworks claim to solve. As Daniel Huly points out, there are are numerous ways that consumers of this can be loaded later than the module, and this will mysteriously not work without leaving the user any way to trace it short of hacking around in the library's compiled code.

Further, while decorators can enforce types on their target, the NestJS ones don't, so anything relying on @Inject completely disposes of type safety:

@Logger('AppService') private logger: Potato;
Enter fullscreen mode Exit fullscreen mode

"We do not want to explicitly write this.logger.setPrefix('AppService') in the constructor of our services?" The mutation is a poor pattern, yes. After all, your service has no real way to know if it's actually TRANSIENT. But otherwise, when something is used in exactly one place, the place that's using it is a perfectly fine place to instantiate it. The DI container is already managing the singleton service.

If you really need a bunch of other injected stuff to do so, expose the factory:

@Injectable()
class SomeService {
  private logger: LoggerService;

  constructor(loggerFactory: LoggerFactory) {
    this.logger = loggerFactory.withPrefix('AppService');
  }
}
Enter fullscreen mode Exit fullscreen mode

It's actually dynamic, it's type-safe, it doesn't make assumptions about whether the consumer actually wants a transient instance or not, you can debug it, and most importantly, it doesn't rely on load order to work. All things a library author should consider.

Thread Thread
 
brunnerlivio profile image
Livio Brunner • Edited

Yes I actually agree. That is one of the reasons why I am personally pushing to always offer decorators alternative.

For me decorators are nice-to-have-syntax-sugars. It's alright for certain things (e.g. defining a Controller), but you always want to have the option to for instance create a Controller dynamically. This can't be done nicely with decorators (except you create class mixins). It's much more convenient to use a service or similar where you can call some functions.

Nonetheless, I believe decorators can be nice. Dynmically generating providers can be useful. I agree - my example wasn't the best - but a lot of the things you've mention (e.g. type-security) can be fixed depending on the context even with Decorators. Libraries authors should strive for a programmatic service-based API and as a nice-to-have maybe an applicable decorator.

In the end its up to the Library Author what they want to offer and how they do it. This article was meant to look into one strategy which might help in your specific use-case. If you -- as a library author -- believe its not the right fit then so be it.

Collapse
 
falven profile image
Francisco Aguilera

underrated comment right here. This is how the nestjs logger works.

Collapse
 
ehaynes99 profile image
Eric Haynes

I just keep banging my head against the wall. It's like some kind of contest to see who can overcomplicate things the most. It's rooted in this archaic belief that initializing objects is expensive, then just snowballs with tactics designed to prevent duplicate initializations across use cases where there would NEVER be duplicates for objects in places where it wouldn't matter if there were.

Collapse
 
lucasmonstro profile image
Lucas Silva

Very nice article :)

Collapse
 
triasbrata profile image
Trias Bratayudhana

Very nice article :)

Collapse
 
aaaeetwo profile image
Жека уже в Гамбурге

Nice work! Thank you.

Collapse
 
ruslangonzalez profile image
Ruslan Gonzalez

Great article... keep sharing things like that, we appreciate it.