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

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

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


当前回答

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

其他回答

我一直很喜欢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

require()和最近的ES模块(import)是管理Node.js中的依赖关系的方法,当然它是直观和有效的,但它也有其局限性。

我的建议是看一看目前Node.js中可用的依赖注入容器,了解它们的优缺点。其中一些是:

awilix injection-js bottlejs inversify node-dependency-injection

举几个例子。

现在真正的问题是,与简单的require()或import相比,使用Node.js DI容器可以实现什么?

优点:

better testability: modules accepts their dependencies as input Inversion of Control: decide how to wire your modules without touching the main code of your application. a customizable algorithm for resolving modules: dependencies have "virtual" identifiers, usually they are not bound to a path on the filesystem. Better extensibility: enabled by IoC and "virtual" identifiers. Other fancy stuff possible: Async initialization Module lifecycle management Extensibility of the DI container itself Can easily implement higher level abstractions (e.g. AOP)

缺点:

与Node.js的“体验”不同:使用DI绝对感觉你偏离了Node的思维方式。 依赖项与其实现之间的关系并不总是明确的。依赖项可能在运行时被解析,并受到各种参数的影响。代码变得更加难以理解和调试 启动时间较慢 大多数DI容器不能很好地与Browserify和Webpack等模块捆绑器一起使用。

与软件开发相关的任何事情一样,在DI或require()/import之间的选择取决于您的需求、系统复杂性和编程风格。

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

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

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

谢谢你!

我是在回答我自己的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
}

这样你的服务就可以工作了,不管你是和谁一起使用 没有依赖注入工具。

我制作《电解质》就是为了这个目的。其他依赖注入解决方案对我来说太有侵入性了,而混淆全局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依赖项,并将实例作为参数注入导出函数。

关键是这是微创的。这个模块是完全可用的,独立于电解质本身。这意味着您的单元测试可以只测试被测试的模块,传入模拟对象,而不需要额外的依赖关系来重新连接内部。

当运行完整的应用程序时,电解质在模块间级别介入,将东西连接在一起,而不需要全局变量、单例或过多的管道。