根据参数注入正确的服务
Posted
技术标签:
【中文标题】根据参数注入正确的服务【英文标题】:Inject correct service based on parameter 【发布时间】:2020-03-30 08:39:11 【问题描述】:假设我有两个模块正在导出 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
但关键是,我不能使用 @nestjs/core
中的 REQUEST
(直接将其注入 useFactory
),因为我在 cron 作业和 API 调用中使用此服务
我也不认为Factory
模式在那里有用,我的意思是它会起作用,但我想正确地做到这一点
我在想property based injection。
但我不确定如何在我的情况下使用它
【问题讨论】:
在下面查看我的答案,看看它是否符合您的需求。这听起来像是使用策略模式的好地方。 ***.com/a/69502460/8148483 【参考方案1】:如果属性是静态的(例如环境变量),您可以使用自定义提供程序来选择合适的实例。但是,如果属性在某种程度上是动态的,则您不能单独依赖嵌套的依赖注入,因为它会在启动时实例化提供程序(REQUEST 范围除外,这不是您的选择)。
静态属性
创建一个custom provider,它基于静态属性(例如环境变量)实例化所需的实现。
provide: AService,
useClass: process.ENV.useBService ? BService : CService,
具有请求范围的动态属性
假设我们有两种不同的服务实现:
@Injectable()
export class BService
public count = 0;
run()
this.count++;
return 'B';
@Injectable()
export class CService
public count = 0;
run()
this.count++;
return 'C';
当两者的count
变量之和为偶数时,应使用BService
; CService
奇怪的时候。为此,我们使用request scope 创建了一个自定义提供程序。
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],
,
如果我们的控制器现在注入 MyService
令牌 (@Inject('MyService')
) 并通过端点公开其 run
方法,它将返回 B
C
B
...
具有默认范围的动态属性
由于我们要使用默认作用域(Singleton!),所以不能使用nest 的依赖注入的静态实例化。相反,您可以使用委托模式在根类中选择所需的实例(在您的示例中为AService
)。
按原样提供所有服务:
providers: [AService, BService, CService]
在您的 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();
【讨论】:
这些解决方案都不符合我的要求,你有没有 docs.nestjs.com/providers#property-based-injection 的工作示例? @KrzysztofSzostak 基于属性的注入只是注入依赖项的另一种方式。您使用实例变量而不是使用构造函数。我看不出这对您的情况有何用处。 Nest 只是不提供您正在寻找的动态实例化。我很确定您只有我列出的三个选项。【参考方案2】:在我看来,工厂方法正是您所需要的。您描述了您需要基于上下文的不同服务,这非常适合工厂方法。让我们试试这个:
创建可注入工厂:
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"`);
现在将该工厂导入您的应用模块:
import ServiceFactory from './ServiceFactory';
import AService from './AService';
@Module(
providers: [AppService, ServiceFactory]
)
export class AppModule
现在您的应用服务将获取工厂作为依赖项,该依赖项将根据上下文创建适当的服务:
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();
【讨论】:
以上是关于根据参数注入正确的服务的主要内容,如果未能解决你的问题,请参考以下文章
运行 PHPUnit 测试时,容器参数在 Symfony 5 服务中不可用/注入