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

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

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


当前回答

我知道这个帖子在这一点上是相当老的,但我想我会在这个问题上发表我的想法。TL;DR是由于JavaScript的无类型、动态特性,您实际上可以在不依赖依赖注入(DI)模式或使用DI框架的情况下做很多事情。然而,随着应用程序变得越来越大、越来越复杂,DI无疑可以帮助提高代码的可维护性。

c# DI

要理解JavaScript中为什么不需要依赖注入,看看强类型语言(如c#)是很有帮助的。(向那些不懂c#的人道歉,但它应该很容易理解。)假设我们有一个应用程序描述了一辆汽车和它的喇叭。你将定义两个类:

class Horn
{
    public void Honk()
    {
        Console.WriteLine("beep!");
    }
}

class Car
{
    private Horn horn;

    public Car()
    {
        this.horn = new Horn();
    }

    public void HonkHorn()
    {
        this.horn.Honk();
    }
}

class Program
{
    static void Main()
    {
        var car = new Car();
        car.HonkHorn();
    }
}

以这种方式编写代码几乎没有什么问题。

The Car class is tightly coupled to the particular implementation of the horn in the Horn class. If we want to change the type of horn used by the car, we have to modify the Car class even though its usage of the horn doesn't change. This also makes testing difficult because we can't test the Car class in isolation from its dependency, the Horn class. The Car class is responsible for the lifecycle of the Horn class. In a simple example like this it's not a big issue, but in real applications dependencies will have dependencies, which will have dependencies, etc. The Car class would need to be responsible for creating the entire tree of its dependencies. This is not only complicated and repetitive, but it violates the "single responsibility" of the class. It should focus on being a car, not creating instances. There is no way to reuse the same dependency instances. Again, this isn't important in this toy application, but consider a database connection. You would typically have a single instance that is shared across your application.

现在,让我们重构它以使用依赖注入模式。

interface IHorn
{
    void Honk();
}

class Horn : IHorn
{
    public void Honk()
    {
        Console.WriteLine("beep!");
    }
}

class Car
{
    private IHorn horn;

    public Car(IHorn horn)
    {
        this.horn = horn;
    }

    public void HonkHorn()
    {
        this.horn.Honk();
    }
}

class Program
{
    static void Main()
    {
        var horn = new Horn();
        var car = new Car(horn);
        car.HonkHorn();
    }
}

我们已经做了两件关键的事情。首先,我们介绍了一个由Horn类实现的接口。这让我们可以将Car类编码到接口,而不是特定的实现。现在代码可以接受任何实现IHorn的东西。其次,我们从Car中取出喇叭实例化,并将其传入。这解决了上述问题,并将其留给应用程序的主要功能来管理特定的实例及其生命周期。

这意味着我们可以在不接触car类的情况下为car引入一种新的喇叭类型:

class FrenchHorn : IHorn
{
    public void Honk()
    {
        Console.WriteLine("le beep!");
    }
}

主程序可以直接注入一个FrenchHorn类的实例。这也极大地简化了测试。您可以创建一个MockHorn类注入到Car构造函数中,以确保您只测试Car类。

上面的例子展示了手动依赖注入。典型的依赖注入是通过框架完成的(例如c#世界中的Unity或Ninject)。这些框架将通过遍历依赖关系图并根据需要创建实例来为您完成所有依赖关系连接。

标准的Node.js方式

现在让我们看看Node.js中的相同示例。我们可能会把代码分成3个模块:

// horn.js
module.exports = {
    honk: function () {
        console.log("beep!");
    }
};

// car.js
var horn = require("./horn");
module.exports = {
    honkHorn: function () {
        horn.honk();
    }
};

// index.js
var car = require("./car");
car.honkHorn();

因为JavaScript是无类型的,所以我们没有以前那样的紧密耦合。不需要接口(也不存在接口),因为car模块只会尝试在horn模块导出的任何内容上调用honk方法。

此外,因为Node需要缓存所有内容,所以模块本质上是存储在容器中的单例。对horn模块执行require的任何其他模块都将获得完全相同的实例。这使得共享单例对象(如数据库连接)非常容易。

现在仍然存在一个问题,即car模块负责获取它自己的依赖喇叭。如果希望汽车使用不同的喇叭模块,则必须更改car模块中的require语句。这不是一件很常见的事情,但它确实会导致测试问题。

通常人们处理测试问题的方法是使用proxyquire。由于JavaScript的动态特性,proxyquire会拦截需要的调用,并返回您提供的存根/mock。

var proxyquire = require('proxyquire');
var hornStub = {
    honk: function () {
        console.log("test beep!");
    }
};

var car = proxyquire('./car', { './horn': hornStub });

// Now make test assertions on car...

这对于大多数应用程序来说已经足够了。如果它适用于你的应用,那就采用它。然而,根据我的经验,随着应用程序变得越来越大、越来越复杂,维护这样的代码变得越来越困难。

JavaScript中的DI

Node.js非常灵活。如果你对上面的方法不满意,你可以使用依赖注入模式来编写你的模块。在此模式中,每个模块导出一个工厂函数(或类构造函数)。

// horn.js
module.exports = function () {
    return {
        honk: function () {
            console.log("beep!");
        }
    };
};

// car.js
module.exports = function (horn) {
    return {
        honkHorn: function () {
            horn.honk();
        }
    };
};

// index.js
var horn = require("./horn")();
var car = require("./car")(horn);
car.honkHorn();

这与前面的c#方法非常相似,因为index.js模块负责实例生命周期和连接。单元测试非常简单,因为您只需将mock /存根传递给函数。同样,如果这对您的应用程序来说足够好,那么就使用它。

Bolus DI框架

与c#不同的是,没有现成的标准依赖注入框架来帮助你管理依赖项。npm注册表中有许多框架,但没有一个被广泛采用。这些选项中的许多已经在其他答案中被引用。

我对所有的选项都不是特别满意,所以我写了自己的bolus。Bolus的设计是为了与上面的DI风格编写的代码一起工作,并试图非常DRY和非常简单。使用上面完全相同的car.js和horn.js模块,你可以用bolus重写index.js模块:

// index.js
var Injector = require("bolus");
var injector = new Injector();
injector.registerPath("**/*.js");

var car = injector.resolve("car");
car.honkHorn();

基本思想是创建一个注入器。你在注入器中注册了所有的模块。然后你只需解决你所需要的。Bolus将遍历依赖关系图,并根据需要创建和注入依赖关系。在这样一个简单的例子中,您不会节省太多,但是在具有复杂依赖树的大型应用程序中,节省的时间是巨大的。

Bolus supports a bunch of nifty features like optional dependencies and test globals, but there are two key benefits I've seen relative to the standard Node.js approach. First, if you have a lot of similar applications, you can create a private npm module for your base that creates an injector and registers useful objects on it. Then your specific apps can add, override, and resolve as needed much like how AngularJS's injector works. Second, you can use bolus to manage various contexts of dependencies. For example, you could use middleware to create a child injector per request, register the user id, session id, logger, etc. on the injector along with any modules depending on those. Then resolve what you need to serve requests. This gives you instances of your modules per request and prevents having to pass the logger, etc. along to every module function call.

其他回答

我自己调查过了。我不喜欢引入神奇的依赖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的人,请原谅它的语法。

看看dips(一个简单而强大的Node.js依赖注入和实体(文件)管理框架)

https://github.com/devcrust/node-dips

我认为我们仍然需要Nodejs中的依赖注入,因为它放松了服务之间的依赖关系,使应用程序更加清晰。

受Spring Framework的启发,我也实现了自己的模块来支持Nodejs中的依赖注入。我的模块还能够检测代码更改并自动重新加载服务,而无需重新启动应用程序。

访问我的项目:Buncha - IoC容器

谢谢你!

我一直很喜欢IoC的简单理念——“你不需要了解任何环境,需要的时候有人会叫你。”

但是我看到的所有IoC实现都完全相反——它们用更多的东西使代码变得混乱。所以,我创建了我自己的IoC,就像我想要的那样-它保持隐藏和不可见的90%的时间。

它用于MonoJS web框架http://monojs.org

我说的是简单的事情,比如共享一个数据库连接对象 但我还没有找到一个让我满意的解决办法。

它是这样做的——在配置中注册组件一次。

app.register 'db', -> 
  require('mongodb').connect config.dbPath

可以在任何地方使用

app.db.findSomething()

你可以在https://github.com/sinizinairina/mono/blob/master/mono.coffee这里看到完整的组件定义代码(包括DB Connection和其他组件)

这是你必须告诉IoC该做什么的唯一地方,之后所有这些组件都将自动创建和连接,你不再需要在应用程序中看到IoC特定的代码。

国际奥委会本身https://github.com/alexeypetrushin/miconjs

I worked with .Net, PHP and Java for long time so I wanted to have a convenient Dependency Injection in NodeJS too. People said the built-in DI in NodeJS is enough as we can get it with Module. But it didn't satisfy me well. I wanted to keep a Module no more than a Class. Additionally, I wanted the DI to have a full support for Module life cycle management (singleton module, transient module etc.) but with Node module, I had to write manual code very often. Lastly, I wanted to make Unit Test easier. That's why I created a Dependency Injection for myself.

如果您正在寻找DI,请尝试一下。可以在这里找到:https://github.com/robo-creative/nodejs-robo-container。它有完整的文档。并对依赖注入中常见的问题进行了分析,并提出了面向对象的解决方法。希望能有所帮助。