DEV Community

Discussion on: Advanced NestJS: Dynamic Providers

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.