NestJS: внедрить сервис (или что-либо еще) в импортированный модуль

#dependency-injection #nestjs

#внедрение зависимостей #nestjs

Вопрос:

Есть ли способ лучше, чем приведенный ниже, внедрить сервис или компонент внутри импортированного модуля?

 export interface AmqpInterceptor{
  after(message:any):Promise<void>;
}

export class AmqpInterceptors extends Array<AmqpInterceptor>{

}

//generic library module
@Module({
  providers:[{
    provide: AmqpInterceptors,
    useValue: []
  }]
}
export class AMQPModule implements OnModuleInit{
  static register(options: AMQPOptions): DynamicModule {
    const providers = options.providers || []
    return {
      module: AMQPModule,
      providers: [
        ...providers,
        OtherProvider
      ]
    }
  }
}

//end user module
@Module({
  imports: [
    AMQPModule.register(({
      // I had to create a factory method to pass providers as an argument.
      // I would think that it is not a good practice
      providers: [{
        provide:AmqpInterceptors,
        useValue:[MyCustomInterceptor]
      }]
    })
  ],
  providers: [
    
  ]
})
export class QueueModule {

}
  

Текущее рабочее решение: я объявляю пустой массив по умолчанию в универсальном модуле и фабричный метод, который позволяет передавать пользовательское значение при построении модуля.(В моем самом счастливом мире я объявляю несколько экземпляров интерфейса, а затем DI собирает все это, но я думаю, что это действительно невозможно в NestJS)

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

1. Вы хотите внедрить сервис в импортированный модуль A, почему бы вам не создать отдельный модуль B, который вы импортируете в A вместо этого?

2. Потому что мне нужно, чтобы модуль A не зависел от какой-либо дополнительной логики. Т.Е. Любой, кто хочет использовать модуль A, должен иметь возможность перехватывать (отслеживать, преобразовывать, регистрировать, трассировать и т.д.) Сообщения. И к тому времени, когда я опубликую «модуль A», этот код даже не будет существовать

Ответ №1:

вы можете использовать пакет, подобный @golevelup / nestjs-discovery, чтобы помочь вам.

В основном вы должны сделать следующее:

// Модуль AMQP

// amqp-interceptor.decorator.ts

 import { SetMetadata } from '@nestjs/common';

export function AmqpInterceptor() {
  return SetMetadata('AMQP_INTERCEPTOR', true)
}
  

// amqp-explorer.ts

 import { OnModuleInit } from '@nestjs/common'

@Injectable()
export class AmqpExplorer implements OnModuleInit {
  constructor(
    private readonly discoveryService: DiscoveryService,
  ) {}

  async onModuleInit(): Promise<void> {
      const amqpInterceptorProviders = await this.discoveryService.providers('AMQP_INTERCEPTOR')

      // you can store this list, to be queried by some
      // other provider to use interceptors, etc.
  }
}
  

// amqp.module.ts

 @Module({ providers:[AmqpExplorer]})
export class AMQPModule { }
  

// модуль конечного пользователя

// end-user.module.ts

 @Module({ imports:[AMQPModule], providers: [SomeAmqpInterceptor] })
export class EndUserModule { }
  

// some-amqp.interceptor.ts

 @Injectable()
@AmqpInterceptor()
export class SomeAmqpInterceptor {
  run(): void { console.log('intercepting') }
}
  

Obs:

  • Декоратор перехватчика: вы можете добавить любые параметры, которые помогли бы вам улучшить ваш api
  • Я предлагаю это (список поставщиков) вместо внедрения оформленных поставщиков в ваш модуль, потому что я не знаю, как вы планируете их использовать. Таким образом, у вас есть список поставщиков, и вы можете вызывать каждого из них.
  • AMQP_INTERCEPTOR строка может конфликтовать с некоторыми другими метаданными, рекомендуется добавлять что-то более конкретное, чтобы избежать двух одинаковых метаданных в модуле
  • Для последующего вызова перехватчиков:
 import { ExternalContextCreator } from '@nestjs/core/helpers/external-context-creator'

export class Runner {
  constructor(
    private readonly handler: DiscoveredMethodWithMeta,
    private readonly externalContextCreator: ExternalContextCreator,
  ) { }

  run(): void {
    const handler = this.externalContextCreator.create(
      this.handler.discoveredMethod.parentClass.instance,
      this.handler.discoveredMethod.handler,
      this.handler.discoveredMethod.methodName,
    )

    const handlerResult = await handler(content)
  }
  
  • Не запускался локально, но я думаю, что это хороший способ начать

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

1. Я думаю, что это блестящее решение. Думаю, я могу даже добавить метаданные приоритета (например, Spring). Большое вам спасибо.

2. Да, все зависит от ваших требований. И если это решит ваш вопрос, отметьте как ответ, пожалуйста: D