我目前正在用nodejs创建一些实验性项目。我用Spring编写过很多Java EE web应用程序,非常欣赏依赖注入的便利性。
现在我很好奇:如何对节点进行依赖注入?或者:我真的需要它吗?因为编程风格不同,是否存在替代概念?
到目前为止,我谈论的是一些简单的事情,比如共享数据库连接对象,但我还没有找到一个让我满意的解决方案。
我目前正在用nodejs创建一些实验性项目。我用Spring编写过很多Java EE web应用程序,非常欣赏依赖注入的便利性。
现在我很好奇:如何对节点进行依赖注入?或者:我真的需要它吗?因为编程风格不同,是否存在替代概念?
到目前为止,我谈论的是一些简单的事情,比如共享数据库连接对象,但我还没有找到一个让我满意的解决方案。
当前回答
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。它有完整的文档。并对依赖注入中常见的问题进行了分析,并提出了面向对象的解决方法。希望能有所帮助。
其他回答
我是在回答我自己的DI模块上的一个问题时发现这个问题的,这个问题是为什么有人需要一个DI系统来进行NodeJS编程。
答案显然倾向于这篇文章中给出的答案:这要看情况。这两种方法都有折衷之处,阅读这个问题的答案可以让你更好地了解它们。
所以,这个问题的真正答案应该是,在某些情况下,你会使用依赖注入系统,在其他情况下则不会。
也就是说,作为开发人员,您希望的是不要在各种应用程序中重复使用您的服务。
这意味着我们应该编写一些可以在依赖注入系统中使用但不绑定到依赖注入库的服务。对我来说,这意味着我们应该像这样编写服务:
module.exports = initDBService;
// Tells any DI lib what it expects to find in it context object
// The $inject prop is the de facto standard for DI imo
initDBService.$inject = ['ENV'];
// Note the context object, imo, a DI tool should bring
// services in a single context object
function initDBService({ ENV }) {
/// actual service code
}
这样你的服务就可以工作了,不管你是和谁一起使用 没有依赖注入工具。
我还写了一个模块来完成这一点,它被称为rewire。只需使用npm安装rewire,然后:
var rewire = require("rewire"),
myModule = rewire("./path/to/myModule.js"); // exactly like require()
// Your module will now export a special setter and getter for private variables.
myModule.__set__("myPrivateVar", 123);
myModule.__get__("myPrivateVar"); // = 123
// This allows you to mock almost everything within the module e.g. the fs-module.
// Just pass the variable name as first parameter and your mock as second.
myModule.__set__("fs", {
readFile: function (path, encoding, cb) {
cb(null, "Success!");
}
});
myModule.readSomethingFromFileSystem(function (err, data) {
console.log(data); // = Success!
});
我受到Nathan MacInnes注射剂的启发,但使用了不同的方法。我不使用vm来评估测试模块,事实上我使用节点自己的要求。这样,您的模块的行为与使用require()完全相同(除了您的修改)。此外,完全支持调试。
我最近创建了一个名为circuitbox的库,它允许你在node.js中使用依赖注入。与我见过的许多基于依赖项查找的库相比,它实现了真正的依赖项注入。Circuitbox还支持异步创建和初始化例程。下面是一个例子:
假设下面的代码位于名为consoleMessagePrinter.js的文件中
'use strict';
// Our console message printer
// deps is injected by circuitbox with the dependencies
function ConsoleMessagePrinter(deps) {
return {
print: function () {
console.log(deps.messageSource.message());
}
};
}
module.exports = ConsoleMessagePrinter;
假设main.js文件中包含以下内容
'use strict';
// our simple message source
// deps is injected by circuitbox with the dependencies
var simpleMessageSource = function (deps) {
return {
message: function () {
return deps.message;
}
};
};
// require circuitbox
var circuitbox = require('../lib');
// create a circuitbox
circuitbox.create({
modules: [
function (registry) {
// the message to be used
registry.for('message').use('This is the message');
// define the message source
registry.for('messageSource').use(simpleMessageSource)
.dependsOn('message');
// define the message printer - does a module.require internally
registry.for('messagePrinter').requires('./consoleMessagePrinter')
.dependsOn('messageSource');
}
]
}).done(function (cbx) {
// get the message printer and print a message
cbx.get('messagePrinter').done(function (printer) {
printer.print();
}, function (err) {
console.log('Could not recieve a printer');
return;
});
}, function (err) {
console.log('Could not create circuitbox');
});
Circuitbox允许您定义组件并将它们的依赖项声明为模块。一旦初始化,它允许您检索组件。Circuitbox自动注入目标组件所需的所有组件,并将其提供给您使用。
该项目处于alpha版本。欢迎您的评论、想法和反馈。
希望能有所帮助!
简而言之,您不需要像在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是我特别不满的地方。
电解质包含模块,特别是那些导出“设置”功能的模块,就像你在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依赖项,并将实例作为参数注入导出函数。
关键是这是微创的。这个模块是完全可用的,独立于电解质本身。这意味着您的单元测试可以只测试被测试的模块,传入模拟对象,而不需要额外的依赖关系来重新连接内部。
当运行完整的应用程序时,电解质在模块间级别介入,将东西连接在一起,而不需要全局变量、单例或过多的管道。