根据参数注入正确的服务

hej*_*ooo 4 javascript dependency-injection node.js typescript nestjs

假设我有两个正在导出的模块,BService并且CService这两个服务都在其中扩展AService

所以代码看起来像这样:

abstract class AService {
    public run() {}
}

@Injectable()
export class BService extends AService {}

@Injectable()
export class CService extends AService {}

@Module({
    providers: [BService],
    exports: [BService],
})
export class BModule {}


@Module({
    providers: [CService],
    exports: [CService],
})
export class CModule {}

@Injectable()
class AppService {
    constructor(protected readonly service: AService) {}

    public run(context: string) { // let's assume context may be B or C
        this.service.run();
    }
}


@Module({
    imports: [CModule, BModule],
    providers: [{
        provide: AppService,
        useFactory: () => {
            return new AppService(); // how to use BService/CService depending on the context?
        }
    }]
})
export class AppModule {}
Run Code Online (Sandbox Code Playgroud)

但关键是,我不能使用REQUEST(直接注入useFactory) from@nestjs/core因为我在 cron 作业和 API 调用中使用此服务

我也不认为Factory模式在那里有用,我的意思是它会起作用,但我想正确地做

我在考虑基于属性的注入

但我不确定如何在我的情况下使用它

Kim*_*ern 6

如果属性是静态的(例如环境变量),您可以使用自定义提供程序来选择正确的实例。但是,如果该属性在某种程度上是动态的,则您不能仅仅依赖 Nest 的依赖注入,因为它会在启动时实例化提供程序(REQUEST 范围除外,这不适合您)。

静态属性

创建一个自定义提供程序,根据静态属性(例如环境变量)实例化所需的实现。

{
  provide: AService,
  useClass: process.ENV.useBService ? BService : CService,
}
Run Code Online (Sandbox Code Playgroud)

具有请求范围的动态属性

假设我们有两种不同的服务实现:

@Injectable()
export class BService {
  public count = 0;
  run() {
    this.count++;
    return 'B';
  }
}

@Injectable()
export class CService {
  public count = 0;
  run() {
    this.count++;
    return 'C';
  }
}
Run Code Online (Sandbox Code Playgroud)

count两者变量之和为偶数时,BService应使用 ;CService当它很奇怪的时候。为此,我们创建一个具有请求范围的自定义提供程序。

{
  provide: 'MyService',
  scope: Scope.REQUEST,
  useFactory: (bService: BService, cService: CService) => {
    if ((bService.count + cService.count) % 2 === 0) {
      return bService;
    } else {
      return cService;
    }
  },
  inject: [BService, CService],
},
Run Code Online (Sandbox Code Playgroud)

如果我们的控制器现在注入MyService令牌( )并通过端点@Inject('MyService')公开其方法,它将返回...runB C B

具有默认范围的动态属性

由于我们想使用默认作用域(Singleton!),因此无法使用 Nest 依赖注入的静态实例化。相反,您可以使用委托模式在根类中选择所需的实例(AService在您的示例中)。

按原样提供所有服务:

providers: [AService, BService, CService]
Run Code Online (Sandbox Code Playgroud)

动态决定AService要使用的实现:

@Injectable()
export class AService {
  constructor(private bService: BService, private cService: CService) {}

  run(dynamicProperty) {
    if (dynamicProperty === 'BService') {
      return this.bService.run();
    } else {
      return this.cService.run();
    }
  }
}
Run Code Online (Sandbox Code Playgroud)


ner*_*ast 6

在我看来,工厂方法正是您所需要的。您描述了您需要基于上下文的不同服务,这非常适合工厂方法。让我们试试这个:

创建一个可注入的工厂:

import { Injectable } from '@nestjs/common';
import { AService } from './AService';
import { BService } from './BService';
import { CService } from './CService';

@Injectable()
export class ServiceFactory {

    public getService(context: string) : AService {

        switch(context) {
            case 'a': return new BService();
            case 'b': return new CService();
            default: throw new Error(`No service defined for the context: "${context}"`);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在将该工厂导入您的应用程序模块:

import { ServiceFactory } from './ServiceFactory';
import { AService } from './AService';

@Module({
    providers: [AppService, ServiceFactory]
})
export class AppModule {}
Run Code Online (Sandbox Code Playgroud)

现在您的应用服务将获得工厂作为依赖项,它将根据上下文创建适当的服务:

import { ServiceFactory } from './ServiceFactory';
import { AService } from './AService';

@Injectable()
class AppService {

    constructor(readonly serviceFactory: ServiceFactory) { }

    public run(context: string) {
        const service: AService = this.serviceFactory.getService(context);
        service.run();
    }
}
Run Code Online (Sandbox Code Playgroud)