我目前正在用nodejs创建一些实验性项目。我用Spring编写过很多Java EE web应用程序,非常欣赏依赖注入的便利性。

现在我很好奇:如何对节点进行依赖注入?或者:我真的需要它吗?因为编程风格不同,是否存在替代概念?

到目前为止,我谈论的是一些简单的事情,比如共享数据库连接对象,但我还没有找到一个让我满意的解决方案。


当前回答

这取决于应用程序的设计。显然,你可以做一个类似java的注入,在那里创建一个类的对象,并将依赖项传递给构造函数,就像这样。

function Cache(store) {
   this._store = store;
}

var cache = new Cache(mysqlStore);

如果你不是在javascript中做OOP,你可以做一个init函数来设置一切。

然而,还有另一种方法可以采用,这种方法在基于事件的系统(如node.js)中更为常见。如果您可以将应用程序建模为仅(大多数时候)对事件进行操作,那么您所需要做的就是设置所有内容(我通常通过调用init函数来完成)并从存根发出事件。这使得测试相当容易和易读。

其他回答

为了很好地测试应用程序,大多数时候最好使用反转控制工具在运行/测试时注入所需的对象。 所以最好不要在模块中直接使用require或import。而是调用DI容器来获取所需的对象。

如果你不想使用第三方库,你可以通过创建一个定制的DI容器来模仿IoC工具的行为。 在测试的时候,你可以模拟你的DI容器,并注入你的假模块用于测试。 下面是一个自定义DI容器的例子,这个版本的容器不支持接口。

myDependecy.js

const myDependecy = {};
export default myDependecy;

myDependecy.myTestFunction = () => {
  console.log("this is as test function.");
};

diContainer.js

import myDependecy from "./myDependecy.js";

const diContainer = {};
export default diContainer;

diContainer.myDependecy = myDependecy;

myModule.js

import diContainer from "./diContainer.js";

function myFunction() {
  diContainer.myDependecy.myTestFunction();
}

我最近检查了这个线程,原因和OP差不多——我遇到的大多数库都临时重写了require语句。我用这种方法取得了不同程度的成功,所以我最终使用了下面的方法。

在一个快速应用程序的上下文中-我将app.js包装在bootstrap.js文件中:

var path = require('path');
var myapp = require('./app.js');

var loader = require('./server/services/loader.js');

// give the loader the root directory
// and an object mapping module names 
// to paths relative to that root
loader.init(path.normalize(__dirname), require('./server/config/loader.js')); 

myapp.start();

传递给加载器的对象映射是这样的:

// live loader config
module.exports = {
    'dataBaseService': '/lib/dataBaseService.js'
}

// test loader config
module.exports = {
    'dataBaseService': '/mocks/dataBaseService.js'
    'otherService' : {other: 'service'} // takes objects too...
};

然后,不要直接调用require…

var myDatabaseService = loader.load('dataBaseService');

如果加载器中没有别名,那么它将默认为常规require。 这样做有两个好处:我可以交换类的任何版本,并且消除了这种需要 在整个应用程序中使用相对路径名(因此如果我需要下面的自定义库 或以上的当前文件,我不需要遍历,并要求将缓存模块针对相同的关键)。它还允许我在应用程序的任何位置指定模拟,而不是在立即的测试套件中。

为了方便起见,我刚刚发布了一个小的npm模块:

https://npmjs.org/package/nodejs-simple-loader

我自己调查过了。我不喜欢引入神奇的依赖utils库,它提供了劫持模块导入的机制。相反,我为我的团队提出了一个“设计指南”,相当明确地说明可以通过在模块中引入工厂函数导出来模拟哪些依赖项。

我大量使用ES6的参数和解构功能,以避免一些样板文件,并提供一个命名依赖项覆盖机制。

这里有一个例子:

import foo from './utils/foo';
import bob from './utils/bob';

// We export a factory which accepts our dependencies.
export const factory = (dependencies = {}) => {
  const {
    // The 'bob' dependency.  We default to the standard 'bob' imp if not provided.
    $bob = bob, 
    // Instead of exposing the whole 'foo' api, we only provide a mechanism
    // with which to override the specific part of foo we care about.
    $doSomething = foo.doSomething // defaults to standard imp if none provided.
  } = dependencies;  

  return function bar() {
    return $bob($doSomething());
  }
}

// The default implementation, which would end up using default deps.
export default factory();

这是它的用法的一个例子

import { factory } from './bar';

const underTest = factory({ $bob: () => 'BOB!' }); // only override bob!
const result = underTest();

对于不熟悉ES6的人,请原谅它的语法。

TypeDI是这里提到的最可爱的,看看TypeDI中的代码

import "reflect-metadata";
import {Service, Container} from "typedi";

@Service()
class SomeClass {

    someMethod() {
    }

}

let someClass = Container.get(SomeClass);
someClass.someMethod();

看看这段代码:

import {Container, Service, Inject} from "typedi";

// somewhere in your global app parameters
Container.set("authorization-token", "RVT9rVjSVN");

@Service()
class UserRepository {

    @Inject("authorization-token")
    authorizationToken: string;

}

我认为其他的文章在使用DI的论证方面做得很好。对我来说,原因是

注入依赖项而不知道它们的路径。这意味着如果您更改磁盘上的模块位置或将其与另一个模块交换,则不需要触及依赖于该模块的每个文件。 它使模拟依赖关系以进行测试变得更加容易,而不用痛苦地重写全局require函数,而且不会出现任何问题。 它可以帮助您将应用程序组织为松散耦合的模块。

但是我很难找到一个我和我的团队可以轻松采用的依赖注入框架。所以我最近基于这些特性构建了一个名为deppie的框架

可以在几分钟内学会的最小API 不需要额外的代码/配置/注释 一对一直接映射需要的模块 可以部分地使用现有代码吗