NodeJS的dependency injection

TLDR – 在NodeJS(ES2015)中更加惯用,为什么?

导出类 ,稍后再实例化:

foo.js:

export default class Foo { } 

bar.js:

 export default class Bar { constructor(foo) { this.foo = foo; } } 

app.js:

 import Foo from './foo'; import Bar from './bar'; new Bar(new Foo()); 

导出实例 – 有效地使它们都是单身人士:

foo.js:

 class Foo { } export default new Foo(); 

bar.js:

 import foo from './foo'; class Bar { } export default new Bar(); 

app.js:

 import bar from './bar'; 

我来自Java世界。 在那里,DI框架通常会创build一些“应用程序上下文” – 它们创build类的实例并将这些对象连接在一起,将它们全部放在一个大包里,从中可以获得所需的任何东西。

静态的“代码导入”( import com.acme.FooService ,它只导入类的定义 )与获取可以调用的实例之间存在巨大的差异。

在NodeJS中做这件事最常见,合理的方法是什么? “导出实例”选项对我来说似乎很脏,感觉像负责加载代码的基础结构也负责实例化对象并将它们连接在一起。 或者,也许这是一个虚假的二分法,这实际上是与Node的方式?

tl; dr :没有理由不能在Node.js中应用dependency injection模式或导出类。

您怀疑导出类的主要原因是,即使它们在应用程序中具有单一生命周期范围,也是JavaScript的黑客般的dynamic特性。

当然,你可以做很多事情,而不依赖于dependency injection模式。

例如,正如你所提到的,你可以导出你的类的实例,从而使它们成为所有的单例。 以这种方式导出的模块甚至不必是任何类的实例。 考虑下面的代码片段:

 export class Foo { foo() { } bar() { } } export default new Foo(); 

与:

 export function foo() { } export function bar() { } 

因此,这是JavaScript生态系统中相当常见的一种方式。 如果不涉及内部模块状态,在所有的意义上都是可以的。 比方说,一个公共接口由一堆函数(如jQuery,lodash等)组成的库。 但是仍然有一些类( Bar在你的例子中)负责获取自己的依赖( Foo )。

你也可以使用一些hack像proxyquire 。 这是人们解决JavaScripttesting问题的常见方式。 有了这样的库,你可以截取需要的调用(虽然不确定是否支持ES6模块语法),并提供自己的模拟/存根用于testing目的。

但是,随着应用程序变得越来越复杂并且变得越来越大,DI可以帮助您的源代码的可维护性。

我认为在JS中应用DI实践的主要问题是不成熟的。 JavaScript中目前的DI解决scheme目前还没有真正受欢迎,所以没有太多的指导方针,没有教程,没有基础设施。 而且,很多自称为DI容器的JavaScript库都是用丑陋的注释来污染模块,或者实际上实现了服务定位器反模式或任何东西。

所以,我认为,最合理的方法是:

  1. 尽可能导出类。 例外情况:您可以将应用程序的Composition Root中构build的实例重新导出为类。
  2. 在不是很复杂的情况下使用纯DI(没有DI容器),或者考虑使用一些DI容器(尽pipe如此,您还是需要Google进行深思熟虑的select)。
  3. 避免过度工程。 例如,不要犹豫,明确地依赖于稳定的API或者你自己的模块在应用程序或基础设施层的一些外部库(我build议你的域/业务逻辑层保持清洁)。

当然,与任何与软件开发相关的东西,在DI或导出单例之间进行select都取决于软件的复杂性和您的要求。