我目前正在用nodejs创建一些实验性项目。我用Spring编写过很多Java EE web应用程序,非常欣赏依赖注入的便利性。
现在我很好奇:如何对节点进行依赖注入?或者:我真的需要它吗?因为编程风格不同,是否存在替代概念?
到目前为止,我谈论的是一些简单的事情,比如共享数据库连接对象,但我还没有找到一个让我满意的解决方案。
我目前正在用nodejs创建一些实验性项目。我用Spring编写过很多Java EE web应用程序,非常欣赏依赖注入的便利性。
现在我很好奇:如何对节点进行依赖注入?或者:我真的需要它吗?因为编程风格不同,是否存在替代概念?
到目前为止,我谈论的是一些简单的事情,比如共享数据库连接对象,但我还没有找到一个让我满意的解决方案。
当前回答
我制作《电解质》就是为了这个目的。其他依赖注入解决方案对我来说太有侵入性了,而混淆全局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依赖项,并将实例作为参数注入导出函数。
关键是这是微创的。这个模块是完全可用的,独立于电解质本身。这意味着您的单元测试可以只测试被测试的模块,传入模拟对象,而不需要额外的依赖关系来重新连接内部。
当运行完整的应用程序时,电解质在模块间级别介入,将东西连接在一起,而不需要全局变量、单例或过多的管道。
其他回答
我最近创建了一个名为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版本。欢迎您的评论、想法和反馈。
希望能有所帮助!
我开发了一个库,用一种简单的方式处理依赖注入,减少了样板代码。每个模块由唯一的名称和控制器函数定义。控制器的参数反映了模块的依赖关系。
阅读更多关于KlarkJS的内容
简单的例子:
KlarkModule(module, 'myModuleName1', function($nodeModule1, myModuleName2) {
return {
log: function() { console.log('Hello from module myModuleName1') }
};
});
myModuleName1是模块的名称。 $nodeModule1是node_module的一个外部库。名称解析为node-module1。前缀$表示它是一个外部模块。 myModuleName2是一个内部模块的名称。 控制器的返回值来自其他内部模块,当它们定义参数myModuleName1时。
我是在回答我自己的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
}
这样你的服务就可以工作了,不管你是和谁一起使用 没有依赖注入工具。
它应该像这样灵活和简单:
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依赖注入 包文档在这里
我希望它能帮到你。
我知道这个帖子在这一点上是相当老的,但我想我会在这个问题上发表我的想法。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.