Как динамически внедрять провайдеров в NestJS

#node.js #dependency-injection #nestjs #metaprogramming

Вопрос:

Моя команда пытается определить способ динамического DI таким образом, чтобы можно было выполнять массовую вставку без необходимости указывать импортировать каждый модуль.

 @Module({
  ...
  providers: [
    WorkerService,
    WorkerResolver,
    Worker2Service,
    Worker2Resolver........
  ]
})
 

хотите достичь

 var allModules = ... // logic here to include all my resolvers, or all my services

@Module({
  ...
  providers: [
    ...allModules
  ]
})
 

Комментарии:

1. Единственный простой способ, который я могу придумать для регистрации каждого поставщика , — это чтение всех файлов, которые экспортирует поставщик (используя соглашение по сравнению с моделью конфигурации ), которую вы хотите зарегистрировать

2. @MicaelLevi Да, вот где мы застряли, DI происходит во время компиляции, и если я попытаюсь прочитать файлы, он сообщит, что в каталоге dist/ ничего не существует.

3. Я думаю, что вы можете обойти это, используя динамические модули

Ответ №1:

Вы можете использовать пакет glob для динамического поиска модулей, а затем использовать функцию динамического модуля NestJS для их динамической загрузки.

Предположим, что все ваши рабочие файлы хранятся в каталоге с именем workers и расширением .worker.ts :

     @Module({})
    export class WorkerModule {
      static forRootAsync(): DynamicModule {
        return {
          module: WorkerModule ,
          imports: [WorkerCoreModule.forRootAsync()],
        };
      }
    }
 
 export class WorkerCoreModule {

  static async forRootAsync(): Promise<DynamicModule> {
    // Feel free to change path if your structure is different
    const workersPath = glob.sync('src/**/workers/*.worker.ts');
    const workersRelativePathWithoutExt = modelsPath
      // Replace src, because you are probably running the code
      // from dist folder
      .map((path) => path.replace('src/', './../'))
      .map((path) => path.replace('.ts', ''));
    const workerProviders: Provider<any>[] = [];
    const importedModules = await Promise.all(
      workersRelativePathWithoutExt.map((path) => import(path)),
    );
    importedModules.forEach((modules) => {
      // Might be different if you are using default export instead
      const worker = modules[Object.keys(modules)[0]];
      workerProviders.push({
        provide: worker.name, 
        useValue: worker,
      });
    });

    return {
      module: WorkerCoreModule,
      providers: [...workerProviders],
      // You can omit exports if providers are meant to be used
      // only in this module
      exports: [...workerProviders],
    };
  }
}
 

Теперь предположим , что у вас есть простой рабочий класс с путем src/anyModule/workers/simple-worker.ts , вы можете использовать его следующим образом:

 class WrokersService {
  constructor(@Inject('SimpleWorker') simpleWroker: SimpleWorker) {}
  .
  .
  .
}
 

Если вы хотите опустить @Inject('SimpleWorker') и автоматически вводить модули, такие как службы NestJS, вам необходимо внести эти изменения в WorkerCoreModule :

 workerProviders.push({
  provide: worker,
});
 

Однако для того, чтобы это сработало, вам нужно быть уверенным, что ваши рабочие классы украшены @injectable() .

Комментарии:

1. Я думаю, что для этого вам все равно нужно внедрить SimpleWorker в WorkerService. Кроме того, если я правильно понимаю ваш подход, он должен предполагать, что все файлы преобразованы 1 в 1 из typescript в javscript. Когда на самом деле наше приложение объединено в один файл. Таким образом, решение должно быть найдено во время компиляции.

2. Вы используете Webpack? Как это оказывается в одном файле?

3. да, мы собираем его в main.js аналогично клиентским приложениям