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

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

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


当前回答

简而言之,您不需要像在c# /Java中那样的依赖注入容器或服务定位器。因为Node.js利用了模块模式,所以没有必要执行构造函数或属性注入。尽管你仍然可以。

JS的伟大之处在于你可以修改任何东西来达到你想要的效果。这在测试时非常有用。

看看我这拙劣的例子吧。

MyClass.js:

var fs = require('fs');

MyClass.prototype.errorFileExists = function(dir) {
    var dirsOrFiles = fs.readdirSync(dir);
    for (var d of dirsOrFiles) {
        if (d === 'error.txt') return true;
    }
    return false;
};

MyClass.test.js:

describe('MyClass', function(){
    it('should return an error if error.txt is found in the directory', function(done){
        var mc = new MyClass();
        assert(mc.errorFileExists('/tmp/mydir')); //true
    });
});

注意到MyClass是如何依赖fs模块的吗?正如@ShatyemShekhar提到的,你确实可以像在其他语言中一样进行构造函数或属性注入。但在Javascript中这是不必要的。

在这种情况下,您可以做两件事。

您可以存根fs。方法,或者在调用require时返回一个完全不同的模块。

方法1:

var oldmethod = fs.readdirSync;
fs.readdirSync = function(dir) { 
    return ['somefile.txt', 'error.txt', 'anotherfile.txt']; 
};

*** PERFORM TEST ***
*** RESTORE METHOD AFTER TEST ****
fs.readddirSync = oldmethod;

方法2:

var oldrequire = require
require = function(module) {
    if (module === 'fs') {
        return {
            readdirSync: function(dir) { 
                return ['somefile.txt', 'error.txt', 'anotherfile.txt']; 
            };
        };
    } else
        return oldrequire(module);
            
}

关键是要利用Node.js和Javascript的强大功能。注意,我是一个CoffeeScript的人,所以我的JS语法可能是不正确的。我并不是说这是最好的方法,但这确实是一种方法。Javascript大师们或许能提供其他解决方案。

更新:

这应该可以解决您关于数据库连接的特定问题。我将创建一个单独的模块来封装数据库连接逻辑。就像这样:

MyDbConnection.js:(请务必选择一个更好的名称)

var db = require('whichever_db_vendor_i_use');

module.exports.fetchConnection() = function() {
    //logic to test connection
    
    //do I want to connection pool?
    
    //do I need only one connection throughout the lifecyle of my application?
    
    return db.createConnection(port, host, databasename); //<--- values typically from a config file    
}

然后,任何需要数据库连接的模块都会包含MyDbConnection模块。

SuperCoolWebApp.js:

var dbCon = require('./lib/mydbconnection'); //wherever the file is stored

//now do something with the connection
var connection = dbCon.fetchConnection(); //mydbconnection.js is responsible for pooling, reusing, whatever your app use case is

//come TEST time of SuperCoolWebApp, you can set the require or return whatever you want, or, like I said, use an actual connection to a TEST database. 

不要一字不差地照搬这个例子。这是一个蹩脚的例子,试图说明您利用模块模式来管理依赖项。希望这对你们有帮助。

其他回答

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

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

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

谢谢你!

它应该像这样灵活和简单:

var MyClass1 = function () {}
var MyClass2 = function (myService1) {
    // myService1.should.be.instanceof(MyClass1); 
}


container.register('myService1', MyClass1);
container.register('myService2', MyClass2, ['myService1']);

我写过一篇关于node.js中的依赖注入的文章。

管理你的服务-node.js依赖注入 包文档在这里

我希望它能帮到你。

简而言之,您不需要像在c# /Java中那样的依赖注入容器或服务定位器。因为Node.js利用了模块模式,所以没有必要执行构造函数或属性注入。尽管你仍然可以。

JS的伟大之处在于你可以修改任何东西来达到你想要的效果。这在测试时非常有用。

看看我这拙劣的例子吧。

MyClass.js:

var fs = require('fs');

MyClass.prototype.errorFileExists = function(dir) {
    var dirsOrFiles = fs.readdirSync(dir);
    for (var d of dirsOrFiles) {
        if (d === 'error.txt') return true;
    }
    return false;
};

MyClass.test.js:

describe('MyClass', function(){
    it('should return an error if error.txt is found in the directory', function(done){
        var mc = new MyClass();
        assert(mc.errorFileExists('/tmp/mydir')); //true
    });
});

注意到MyClass是如何依赖fs模块的吗?正如@ShatyemShekhar提到的,你确实可以像在其他语言中一样进行构造函数或属性注入。但在Javascript中这是不必要的。

在这种情况下,您可以做两件事。

您可以存根fs。方法,或者在调用require时返回一个完全不同的模块。

方法1:

var oldmethod = fs.readdirSync;
fs.readdirSync = function(dir) { 
    return ['somefile.txt', 'error.txt', 'anotherfile.txt']; 
};

*** PERFORM TEST ***
*** RESTORE METHOD AFTER TEST ****
fs.readddirSync = oldmethod;

方法2:

var oldrequire = require
require = function(module) {
    if (module === 'fs') {
        return {
            readdirSync: function(dir) { 
                return ['somefile.txt', 'error.txt', 'anotherfile.txt']; 
            };
        };
    } else
        return oldrequire(module);
            
}

关键是要利用Node.js和Javascript的强大功能。注意,我是一个CoffeeScript的人,所以我的JS语法可能是不正确的。我并不是说这是最好的方法,但这确实是一种方法。Javascript大师们或许能提供其他解决方案。

更新:

这应该可以解决您关于数据库连接的特定问题。我将创建一个单独的模块来封装数据库连接逻辑。就像这样:

MyDbConnection.js:(请务必选择一个更好的名称)

var db = require('whichever_db_vendor_i_use');

module.exports.fetchConnection() = function() {
    //logic to test connection
    
    //do I want to connection pool?
    
    //do I need only one connection throughout the lifecyle of my application?
    
    return db.createConnection(port, host, databasename); //<--- values typically from a config file    
}

然后,任何需要数据库连接的模块都会包含MyDbConnection模块。

SuperCoolWebApp.js:

var dbCon = require('./lib/mydbconnection'); //wherever the file is stored

//now do something with the connection
var connection = dbCon.fetchConnection(); //mydbconnection.js is responsible for pooling, reusing, whatever your app use case is

//come TEST time of SuperCoolWebApp, you can set the require or return whatever you want, or, like I said, use an actual connection to a TEST database. 

不要一字不差地照搬这个例子。这是一个蹩脚的例子,试图说明您利用模块模式来管理依赖项。希望这对你们有帮助。

为了很好地测试应用程序,大多数时候最好使用反转控制工具在运行/测试时注入所需的对象。 所以最好不要在模块中直接使用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();
}

对于ES6,我开发了这个容器 https://github.com/zazoomauro/node-dependency-injection

import {ContainerBuilder} from 'node-dependency-injection'

let container = new ContainerBuilder()
container.register('mailer', 'Mailer')

然后,您可以设置,例如,在集装箱中的运输选择:

import {ContainerBuilder} from 'node-dependency-injection'

let container = new ContainerBuilder()
container
  .register('mailer', 'Mailer')
  .addArgument('sendmail')

这个类现在灵活得多,因为您已经将传输的选择从实现中分离出来,并将其放入容器中。

现在邮件服务已经在容器中,您可以将其作为其他类的依赖项注入。如果你有一个这样的newsletter ttermanager类:

class NewsletterManager {
    construct (mailer, fs) {
        this._mailer = mailer
        this._fs = fs
    }
}

export default NewsletterManager

在定义newsletter tter_manager服务时,邮件服务还不存在。使用Reference类告诉容器在初始化通讯管理器时注入mailer服务:

import {ContainerBuilder, Reference, PackageReference} from 'node-dependency-injection'
import Mailer from './Mailer'
import NewsletterManager from './NewsletterManager'

let container = new ContainerBuilder()

container
  .register('mailer', Mailer)
  .addArgument('sendmail')

container
  .register('newsletter_manager', NewsletterManager)
  .addArgument(new Reference('mailer'))
  .addArgument(new PackageReference('fs-extra'))

你也可以用Yaml、Json或JS文件等配置文件来设置容器

由于各种原因,可以编译服务容器。这些原因包括检查任何潜在的问题,如循环引用和使容器更高效。

container.compile()