我目前正在用nodejs创建一些实验性项目。我用Spring编写过很多Java EE web应用程序,非常欣赏依赖注入的便利性。
现在我很好奇:如何对节点进行依赖注入?或者:我真的需要它吗?因为编程风格不同,是否存在替代概念?
到目前为止,我谈论的是一些简单的事情,比如共享数据库连接对象,但我还没有找到一个让我满意的解决方案。
我目前正在用nodejs创建一些实验性项目。我用Spring编写过很多Java EE web应用程序,非常欣赏依赖注入的便利性。
现在我很好奇:如何对节点进行依赖注入?或者:我真的需要它吗?因为编程风格不同,是否存在替代概念?
到目前为止,我谈论的是一些简单的事情,比如共享数据库连接对象,但我还没有找到一个让我满意的解决方案。
当前回答
我认为其他的文章在使用DI的论证方面做得很好。对我来说,原因是
注入依赖项而不知道它们的路径。这意味着如果您更改磁盘上的模块位置或将其与另一个模块交换,则不需要触及依赖于该模块的每个文件。 它使模拟依赖关系以进行测试变得更加容易,而不用痛苦地重写全局require函数,而且不会出现任何问题。 它可以帮助您将应用程序组织为松散耦合的模块。
但是我很难找到一个我和我的团队可以轻松采用的依赖注入框架。所以我最近基于这些特性构建了一个名为deppie的框架
可以在几分钟内学会的最小API 不需要额外的代码/配置/注释 一对一直接映射需要的模块 可以部分地使用现有代码吗
其他回答
Node.js和其他平台一样需要DI。如果您正在构建一些大的东西,DI将使您更容易模拟代码的依赖关系并彻底测试代码。
例如,数据库层模块不应该只在业务代码模块中使用,因为在单元测试这些业务代码模块时,dao将加载并连接到数据库。
一种解决方案是将依赖项作为模块参数传递:
module.exports = function (dep1, dep2) {
// private methods
return {
// public methods
test: function(){...}
}
}
通过这种方式,依赖关系可以轻松自然地模拟,你可以专注于测试你的代码,而不需要使用任何棘手的第三方库。
还有其他的解决方案(百老汇,建筑师等)可以帮助你解决这个问题。尽管他们可能做的比你想要的多,或者使用更多的杂物。
我开发了一个库,用一种简单的方式处理依赖注入,减少了样板代码。每个模块由唯一的名称和控制器函数定义。控制器的参数反映了模块的依赖关系。
阅读更多关于KlarkJS的内容
简单的例子:
KlarkModule(module, 'myModuleName1', function($nodeModule1, myModuleName2) {
return {
log: function() { console.log('Hello from module myModuleName1') }
};
});
myModuleName1是模块的名称。 $nodeModule1是node_module的一个外部库。名称解析为node-module1。前缀$表示它是一个外部模块。 myModuleName2是一个内部模块的名称。 控制器的返回值来自其他内部模块,当它们定义参数myModuleName1时。
简而言之,您不需要像在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.
不要一字不差地照搬这个例子。这是一个蹩脚的例子,试图说明您利用模块模式来管理依赖项。希望这对你们有帮助。
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。它有完整的文档。并对依赖注入中常见的问题进行了分析,并提出了面向对象的解决方法。希望能有所帮助。
我制作《电解质》就是为了这个目的。其他依赖注入解决方案对我来说太有侵入性了,而混淆全局require是我特别不满的地方。
电解质包含模块,特别是那些导出“设置”功能的模块,就像你在Connect/Express中间件中看到的那样。本质上,这些类型的模块只是它们所返回的对象的工厂。
例如,创建数据库连接的模块:
var mysql = require('mysql');
exports = module.exports = function(settings) {
var connection = mysql.createConnection({
host: settings.dbHost,
port: settings.dbPort
});
connection.connect(function(err) {
if (err) { throw err; }
});
return connection;
}
exports['@singleton'] = true;
exports['@require'] = [ 'settings' ];
您在底部看到的是注释,这是一个额外的元数据,电解质使用它来实例化和注入依赖关系,自动将应用程序的组件连接在一起。
创建一个数据库连接。
var db = electrolyte.create('database');
电解质传递遍历@require'd依赖项,并将实例作为参数注入导出函数。
关键是这是微创的。这个模块是完全可用的,独立于电解质本身。这意味着您的单元测试可以只测试被测试的模块,传入模拟对象,而不需要额外的依赖关系来重新连接内部。
当运行完整的应用程序时,电解质在模块间级别介入,将东西连接在一起,而不需要全局变量、单例或过多的管道。