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

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

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


当前回答

对于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()

其他回答

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之间的选择取决于您的需求、系统复杂性和编程风格。

TypeDI是这里提到的最可爱的,看看TypeDI中的代码

import "reflect-metadata";
import {Service, Container} from "typedi";

@Service()
class SomeClass {

    someMethod() {
    }

}

let someClass = Container.get(SomeClass);
someClass.someMethod();

看看这段代码:

import {Container, Service, Inject} from "typedi";

// somewhere in your global app parameters
Container.set("authorization-token", "RVT9rVjSVN");

@Service()
class UserRepository {

    @Inject("authorization-token")
    authorizationToken: string;

}

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

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

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

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

看看dips(一个简单而强大的Node.js依赖注入和实体(文件)管理框架)

https://github.com/devcrust/node-dips