我需要在NodeJS中进行依赖注入,还是要处理……?


219

我目前正在使用nodejs创建一些实验项目。我已经用Spring编写了很多Java EE Web应用程序,并赞赏那里的依赖注入的简易性。

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

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


1
如果您决定使用DI,则OpenTable最近为其开源了一个库:github.com/opentable/spur-ioc我已经使用了它(我在那儿工作),并且可以说它非常简单并且非常适合测试。
tybro0103

Answers:


107

简而言之,您不需要像C#/ Java中那样的依赖项注入容器或服务定位器。由于Node.js利用module pattern,因此无需执行构造函数或属性注入。虽然您仍然可以。

JS的伟大之处在于,您可以修改几乎所有内容以实现所需的功能。在进行测试时,这很方便。

看我非常very脚的人为例子。

MyClass.js

var fs = require('fs');

MyClass.prototype.errorFileExists = function(dir) {
    var dirsOrFiles = fs.readdirSync(dir);
    for (var d in 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.readdirSync方法进行存根,也可以在调用时返回完全不同的模块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. 

不要逐字遵循此示例。在尝试传达您利用module模式来管理依赖项时,这是一个la脚的例子。希望这会有所帮助。


42
就测试而言,这是正确的,但DI具有其他好处;通过使用DI,您可以编程为接口而不是实现。
moteutsch 2012年

3
@moteutsch不知道为什么要这么做,因为JS没有像大多数静态语言一样的接口概念。您真正拥有的只是实现,即使您想使用在文档化的“接口”上预先约定的功能。
JP理查森

16
@JPRichardson如何编写不依赖任何库的使用记录器的组件?如果使用I require('my_logger_library'),则使用我的组件的人将不得不覆盖使用他们自己的库的要求。相反,我可以允许人们传递将记录器实现包装到组件“构造函数”或“初始化”方法中的回调。那就是DI的目的。
moteutsch 2012年

4
截至2014年中-npmjs.org/package/proxyquire 使得模拟 “ require”依赖关系变得微不足道。
arcseldon

4
我不明白,在一个模块中替换require并不会在另一个模块中替换它。如果我在测试中将功能设置为require,然后要求对模块进行测试,则要测试的对象中的require语句不要使用测试模块中设置的功能。这如何注入依赖关系?
HMR 2014年

72

require是在Node.js中管理依赖项方法,它肯定是直观且有效的,但也有其局限性。

我的建议是看一下当今可用于Node.js的一些依赖注入容器,以了解其优缺点。他们之中有一些是:

仅举几个。

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

优点:

  • 更好的可测试性:模块接受其依赖性作为输入
  • 控制反转:确定如何在不触摸应用程序主要代码的情况下连接模块。
  • 用于解决模块的可定制算法:依赖项具有“虚拟”标识符,通常它们不绑定到文件系统上的路径。
  • 更好的扩展性:通过IoC和“虚拟”标识符实现。
  • 其他可能的花哨的东西:
    • 异步初始化
    • 模块生命周期管理
    • DI容器本身的可扩展性
    • 可以轻松实现更高级别的抽象(例如AOP)

缺点:

  • 与Node.js的“体验”不同:require绝对不使用感觉就像您偏离了Node的思维方式。
  • 依赖关系及其实现之间的关系并不总是明确的。依赖性可以在运行时解决,并受各种参数影响。代码变得更加难以理解和调试
  • 较慢的启动时间
  • 成熟度(目前):目前没有一种解决方案真正受欢迎,因此没有那么多的教程,没有生态系统,没有经过战斗测试。
  • 某些DI容器在诸如Browserify和Webpack之类的模块捆绑器中不能很好地发挥作用。

与任何与软件开发相关的内容一样,在DI或其他require条件之间进行选择取决于您的要求,系统复杂性和编程风格。


3
您是否认为自09年以来情况发生了很大变化?
JuhoVepsäläinen2013年

13
您是说10天前开始吗?:)
Mario

2
不 12月9日...应该知道。
JuhoVepsäläinen13年

4
我使用module.exports = function(deps){}这种模式来“实现” DI。是的,它可以工作,但不是很理想。
JuhoVepsäläinen13年

3
模块接受它们的依赖关系作为输入,依赖关系对我来说并不是矛盾的明确声音。
Anton Rudeshko 2014年

53

我知道这个线程在这一点上已经相当老了,但是我认为我对此很感兴趣。TL; DR的原因在于,由于JavaScript具有无类型的动态特性,因此您实际上可以做很多事情而无需诉诸于依赖注入(DI)模式或使用DI框架。但是,随着应用程序变得越来越大,越来越复杂,DI无疑可以帮助您维护代码的可维护性。

C#中的DI

要了解为什么在JavaScript中DI并没有那么重要,研究一下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();
    }
}

这样编写代码几乎没有问题。

  1. 所述Car类被紧密耦合到特定的实施方式中的喇叭的Horn类。如果要更改汽车使用的喇叭的类型,则Car即使该喇叭的用法不变,我们也必须修改该类。这也使测试变得困难,因为我们无法将Car类与其依赖项(即Horn类)隔离开来进行测试。
  2. Car班负责的生命周期Horn类。在像这样的简单示例中,这不是什么大问题,但是在实际应用程序中,依赖项将具有依赖项,而依赖项将具有依赖项等。Car类将需要负责创建其依赖项的整个树。这不仅复杂而且重复,而且违反了班级的“单一责任”。它应该专注于成为一辆汽车,而不是创建实例。
  3. 无法重用相同的依赖项实例。同样,这在此玩具应用程序中并不重要,但请考虑数据库连接。通常,您将拥有一个在整个应用程序中共享的实例。

现在,让我们重构它以使用依赖项注入模式。

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

class FrenchHorn : IHorn
{
    public void Honk()
    {
        Console.WriteLine("le beep!");
    }
}

main可以只注入FrenchHorn类的实例。这也大大简化了测试。您可以创建一个MockHorn类以注入到Car构造函数中,以确保仅Car在隔离状态下测试该类。

上面的示例显示了手动依赖项注入。通常,DI是通过框架(例如,C#世界中的UnityNinject)完成的。这些框架将通过遍历依赖图并根据需要创建实例来为您完成所有依赖关系接线。

标准的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模块只会尝试honkhorn模块导出的任何内容上调用该方法。

另外,由于Node require缓存了所有内容,因此模块本质上是存储在容器中的单例。require在该horn模块上执行的任何其他模块将获得完全相同的实例。这使得共享单例对象(如数据库连接)变得非常容易。

现在仍然存在car模块负责获取其自身依赖关系的问题horn。如果您想让汽车的喇叭使用其他模块,则必须更改模块中的require语句car。这不是很常见的事情,但是确实会导致测试问题。

人们处理测试问题的通常方法是使用proxyquire。由于JavaScript具有动态特性,proxyquire会拦截对require的调用并返回您提供的所有存根/模拟。

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模块负责实例生命周期和接线。单元测试非常简单,因为您只需将模拟/存根传递给函数即可。同样,如果这对您的应用程序足够好,请继续使用它。

Bolus DI框架

与C#不同,没有建立的标准DI框架可帮助您进行依赖管理。npm注册表中有许多框架,但没有一个被广泛采用。在其他答案中已经引用了许多这些选项。

我对任何可用的选项都不满意,所以我写了自己的bolus。Bolus旨在与上面以DI风格编写的代码一起使用,并试图使其非常干燥且非常简单。使用完全相同的car.jshorn.js模块上方,你可以重写index.js与丸的模块:

// index.js
var Injector = require("bolus");
var injector = new Injector();
injector.registerPath("**/*.js");

var car = injector.resolve("car");
car.honkHorn();

基本思想是创建一个注射器。您可以在进样器中注册所有模块。然后,您只需解决所需的内容。Bolus将遍历依赖关系图,并根据需要创建和注入依赖关系。在像这样的玩具示例中,您不会节省太多,但是在具有复杂依赖树的大型应用程序中,节省的费用是巨大的。

Bolus支持许多漂亮的功能,例如可选的依赖项和测试全局变量,但是相对于标准的Node.js方法,我看到了两个主要优点。首先,如果您有很多类似的应用程序,则可以为基础创建一个私有npm模块,该模块创建一个注入器并在其上注册有用的对象。然后,您的特定应用可以根据需要添加,覆盖和解析,就像AngularJS的方式一样喷油器工作。其次,您可以使用推注来管理各种依赖性上下文。例如,您可以使用中间件为每个请求创建子注入器,并在注入器上注册用户ID,会话ID,记录器等,以及依赖于这些模块的任何模块。然后解决您需要处理请求的问题。这为您提供了每个请求的模块实例,并避免了将记录器等传递给每个模块函数调用。


1
的确存在proxyquire诸如这样的替代方法,sinon它允许您进行非常简洁的模拟,例如let readFileStub = sinon.stub(fs, 'readFile').yields(new Error('something went wrong'));,随后对的调用fs.readFile将返回错误,直到您通过还原存根为止readFileStub.restore()。我个人认为DI的使用值得怀疑,因为我觉得它几乎需要使用类/对象,考虑到javascript的功能倾向,这是一个可疑的假设。
凯文(Kevin)

感谢您提供良好的详细答案。当我第一次阅读C#中的标题DI时,我几乎错过了它。
Konstantin A. Magg's

1
好答案。我想知道你的想法是什么在2019年对于大型项目,因为个人喜好的问题,您选哪- DI /国际奥委会节点,或者只是磕碰/与嘲讽jestrewireproxyquire,等?谢谢。
杰米·科克希尔

伟大的平衡答案!谢谢。
Johnny Oshika

36

我还编写了一个模块来完成此任务,称为rewire。只需使用npm install 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()(除了您的修改)。完全支持调试。


7
截至2014年中-npmjs.org/package/proxyquire 使得模拟 “ require”依赖关系变得微不足道。
arcseldon

proxyquire也很酷!他们现在正在使用内部“模块”模块,该模块比使用节点的虚拟机更好。但是毕竟这只是样式问题。我希望我的模块使用原始的require并在以后交换掉依赖项。此外,重布线还允许覆盖全局变量。
约翰内斯·埃瓦尔德

寻找类似的东西在工作中使用非常有趣,这个模块也会影响下游模块吗?
akst 2014年

对于proxyquire它在它的用于测试的描述据说,“代理服务器需要的NodeJS,以允许在覆盖的依赖性测试。” 不是DI吧?
Marwen Trabelsi

17

我为此目的而建造了Electrolyte。其他的依赖注入解决方案对我的口味来说太具有侵入性,而与全局混乱require是我的特别不满。

Electrolyte包含模块,特别是那些导出“设置”功能的模块,就像您在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' ];

您在底部看到的是注释,这是Electrolyte用来实例化和注入依赖项的额外元数据,可自动将应用程序的组件连接在一起。

要创建数据库连接:

var db = electrolyte.create('database');

电解质可传递地遍历@require'd依赖关系,并将实例作为自变量注入到导出函数中。

关键是这是微创的。该模块完全可用,与电解质本身无关。这意味着您的单元测试可以测试被测模块,传递模拟对象,而无需其他依赖来重新连接内部。

当运行整个应用程序时,Electrolyte在模块间级别介入,无需将全局变量,单例对象或过多的管道连接在一起即可将它们连接在一起。


1
您能否澄清调用connect()引发时在已发布的代码中会发生什么?即使我对Node的MySql API并不熟悉,我也希望此调用是异步的,因此插图并不十分清楚。
Andrey Agibalov 2015年

目前正在使用电解质。您声称很容易通过导出['@require']来注入模块。但是,如果我必须使用必需的模块之一,那么在电解质中如何实现该模块。当前,如果我们需要模块,则可以轻松实现。但是对于电解质来说,这是一个巨大的负面影响。...您是否有示例,可以使用模块的存根版本,并在从测试用例进行实例化/ioc.use时动态传递它。因此,基本上在单元测试中,如果我们可以做ioc.create('modulename')然后注入依赖模块(但存根模块),则将是理想的……
user1102171

1
您不会ioc.create从单元测试中调用。单元测试应测试被测模块,而不引入其他依赖项,包括电解质。遵循此建议,您可以做objToTest = require('modulename')(mockObj1, mockObj2);
Jared Hanson

8

我自己调查了一下。我不喜欢引入魔术依赖性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的语法感到抱歉。


确实很巧妙!
阿尔诺德

4

我最近检查该线程的原因与OP大致相同-我遇到的大多数库都临时重写了require语句。我用这种方法取得了不同程度的成功,因此我最终使用了以下方法。

在快速应用程序的上下文中-我将app.js包装在bootstrap.js文件中:

var path = require('path');
var myapp = require('./app.js');

var loader = require('./server/services/loader.js');

// give the loader the root directory
// and an object mapping module names 
// to paths relative to that root
loader.init(path.normalize(__dirname), require('./server/config/loader.js')); 

myapp.start();

传递给加载器的对象映射如下所示:

// live loader config
module.exports = {
    'dataBaseService': '/lib/dataBaseService.js'
}

// test loader config
module.exports = {
    'dataBaseService': '/mocks/dataBaseService.js'
    'otherService' : {other: 'service'} // takes objects too...
};

然后,而不是直接致电require ...

var myDatabaseService = loader.load('dataBaseService');

如果加载程序中没有别名-那么它将默认为常规需求。这有两个好处:我可以交换该类的任何版本,并且它消除了在整个应用程序中使用相对路径名的需要(因此,如果我需要在当前文件之下或之上的自定义lib,则无需遍历,并且require将针对相同的密钥缓存模块)。它还使我可以在应用程序中的任何地方(而不是在即时测试套件中)指定模拟。

为了方便起见,我刚刚发布了一个小npm模块:

https://npmjs.org/package/nodejs-simple-loader


3

现实情况是,您可以在没有IoC容器的情况下测试node.js,因为JavaScript是一种真正的动态编程语言,并且您可以在运行时修改几乎所有内容。

考虑以下:

import UserRepository from "./dal/user_repository";

class UserController {
    constructor() {
        this._repository = new UserRepository();
    }
    getUsers() {
        this._repository.getAll();
    }
}

export default UserController;

因此,您可以在运行时覆盖组件之间的耦合。我想认为我们应该以解耦我们的JavaScript模块为目标。

实现真正去耦的唯一方法是删除对的引用UserRepository

class UserController {
    constructor(userRepository) {
        this._repository = userRepository;
    }
    getUsers() {
        this._repository.getAll();
    }
}

export default UserController;

这意味着您将需要在其他地方进行对象组合:

import UserRepository from "./dal/user_repository";
import UserController from "./dal/user_controller";

export default new UserController(new UserRepository());

我喜欢将对象组合委托给IoC容器的想法。您可以在文章JavaScript中依赖项反转的当前状态中了解有关此想法的更多信息。本文试图揭穿一些“ JavaScript IoC容器神话”:

误解1:JavaScript中没有IoC容器的地方

误区二:我们不需要IoC容器,我们已经有了模块加载器!

误解三:依赖倒置===注入依赖

如果您也喜欢使用IoC容器的想法,可以看看InversifyJS。最新版本(2.0.0)支持许多用例:

  • 内核模块
  • 内核中间件
  • 使用类,字符串文字或符号作为依赖项标识符
  • 注入常​​数
  • 注入类构造函数
  • 注入工厂
  • 汽车工厂
  • 注入提供者(异步工厂)
  • 激活处理程序(用于注入代理)
  • 多次注射
  • 标记绑定
  • 自定义标签装饰器
  • 命名绑定
  • 上下文绑定
  • 友好的异常(例如循环依赖)

您可以在InversifyJS上了解更多信息。


2

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

由于您已经将传输的选择从实现中分离到了容器中,因此该类现在更加灵活。

现在,邮件服务已在容器中,您可以将其作为其他类的依赖项进行注入。如果您有一个类似NewsletterManager的类:

class NewsletterManager {
    construct (mailer, fs) {
        this._mailer = mailer
        this._fs = fs
    }
}

export default NewsletterManager

在定义newsletter_manager服务时,邮件服务尚不存在。使用Reference类告诉容器在初始化新闻通讯管理器时注入邮件服务:

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

1

这取决于您的应用程序的设计。显然,您可以执行类似Java的注入,在其中创建类的对象,并在构造函数中传递这样的依赖关系。

function Cache(store) {
   this._store = store;
}

var cache = new Cache(mysqlStore);

如果您不使用JavaScript进行OOP,则可以创建一个初始化函数来设置所有内容。

但是,您可以采用另一种方法,这种方法在基于事件的系统(例如node.js)中更为常见。如果您可以将应用程序建模为仅(在大多数情况下)对事件起作用,那么您所需要做的就是设置一切(我通常通过调用init函数来完成)并从存根发出事件。这使得测试相当容易且可读。


感谢您的回答,但我不完全理解您回答的第二部分。
埃里克(Erik)2012年

1

我一直喜欢IoC概念的简单性-“您不必了解任何环境,需要时就会有人打电话给您”

但是我看到的所有IoC实现都完全相反-它们使代码杂乱无章。因此,我创建了自己的IoC,该IoC可以按我希望的方式工作-90%的时间保持隐藏和不可见

在MonoJS Web框架http://monojs.org中使用

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

就是这样-在config中注册一次组件。

app.register 'db', -> 
  require('mongodb').connect config.dbPath

并在任何地方使用

app.db.findSomething()

您可以在这里https://github.com/sinizinairina/mono/blob/master/mono.coffee看到完整的组件定义代码(带有DB Connection和其他组件)。

在必须告诉IoC该怎么做之后,这是唯一的地方,之后所有这些组件都将自动创建并连接,并且您不再需要在应用程序中看到IoC特定的代码。

IoC本身https://github.com/alexeypetrushin/miconjs


6
尽管宣传为DI,但这似乎更像是服务定位器。
KyorCode 2014年

2
看起来很棒,可惜它只在咖啡中
Rafael P. Miranda

1

我认为我们仍需要在Node.js中进行依赖注入,因为它可以放宽服务之间的依赖关系,并使应用程序更清晰。

Spring Framework的启发,我还实现了自己的模块以支持Node.js中的依赖注入。我的模块也能够检测code changesauto reload服务,而无需重新启动应用程序。

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

谢谢!



0

我长期使用.Net,PHP和Java,因此我也想在NodeJS中使用方便的依赖注入。人们说,NodeJS中的内置DI足够了,因为我们可以通过Module获得它。但这并不令我满意。我想保留一个模块不超过一个类。另外,我希望DI对模块生命周期管理(单模块,瞬态模块等)提供全面支持,但是对于Node模块,我不得不经常编写手动代码。最后,我想简化单元测试。这就是为什么我为自己创建了一个依赖注入。

如果您正在寻找DI,请尝试一下。可以在这里找到:https : //github.com/robo-creative/nodejs-robo-container。完全记录在案。它还解决了DI的一些常见问题以及如何以OOP方式解决它们。希望能帮助到你。


是的,您是对的,您的项目中的DI库对于良好的体系结构很重要。如果您想查看DI的用例,请参阅此存储库的自述文件以及节点Jems DI的DI库。
弗朗西斯科·梅赛德斯

-1

我最近创建了一个名为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版本。欢迎您提出意见,想法和反馈。

希望能帮助到你!


-1

我认为其他职位在使用DI的论点上做得很好。对我来说,原因是

  1. 在不知道其路径的情况下注入依赖项。这意味着,如果您更改磁盘上的模块位置或与另一个模块交换位置,则无需触摸依赖于该模块的每个文件。

  2. 它使模拟测试依赖关系变得容易得多,而无需以require没有问题的方式重写全局函数的痛苦。

  3. 它可以帮助您将应用程序组织和推理为松散耦合的模块。

但是我很难找到一个我和我的团队可以轻松采用的DI框架。所以我最近基于这些功能构建了一个名为deppie的框架

  • 几分钟即可掌握的最少API
  • 无需额外的代码/配置/注释
  • 一对一直接映射到require模块
  • 可以部分采用以与现有代码一起使用

-1

它应该像这样灵活而简单:

var MyClass1 = function () {}
var MyClass2 = function (myService1) {
    // myService1.should.be.instanceof(MyClass1); 
}


container.register('myService1', MyClass1);
container.register('myService2', MyClass2, ['myService1']);

我在node.js中写了有关依赖注入的文章。

我希望它可以帮助您。


-1

Node.js与其他平台一样需要DI。如果您要构建大型项目,DI将使模拟代码依赖关系和彻底测试代码变得更加容易。

例如,您的数据库层模块不仅仅需要在您的业务代码模块中使用,因为在对这些业务代码模块进行单元测试时,dao将加载并连接到数据库。

一种解决方案是将依赖项作为模块参数传递:

module.exports = function (dep1, dep2) {
// private methods

   return {
    // public methods
       test: function(){...}
   }
}

这样,可以轻松自然地模拟依赖关系,并且您可以专注于测试代码,而无需使用任何棘手的第三方库。

还有其他解决方案(百老汇,建筑师等)可以为您提供帮助。尽管他们可能做的比您想要的更多,或使用起来更混乱。


几乎通过自然进化,我最终都做了同样的事情。我将依赖项作为参数传递,它非常适合测试。
munkee

-1

我开发了一个库,该库以一种简单的方式处理依赖项注入,从而减少了样板代码。每个模块均由唯一的名称和控制器功能定义。控制器的参数反映了模块的依赖性。

阅读更多关于KlarkJS的信息

简要示例:

KlarkModule(module, 'myModuleName1', function($nodeModule1, myModuleName2) {
    return {
        log: function() { console.log('Hello from module myModuleName1') }
    };
});
  • myModuleName1 是模块的名称。
  • $nodeModule1是的外部库node_module。名称解析为node-module1。前缀$表示它是一个外部模块。
  • myModuleName2 是内部模块的名称。
  • 当其他内部模块定义参数时,将使用控制器的返回值myModuleName1

-1

回答我自己的DI模块上的一个问题时,我发现了这个问题,询问为什么有人需要使用DI系统进行NodeJS编程。

答案显然倾向于该线程中给出的答案:这取决于。两种方法都需要权衡,阅读此问题的答案可以很好地解决它们。

因此,此问题的真正答案应该是,在某些情况下,您将使用DI系统,而在其他情况下则不会。

就是说,作为开发人员,您想要的是不要重复自己,并在各种应用程序中重用您的服务。

这意味着我们应该编写可以在DI系统中使用但不依赖于DI库的服务。对我来说,这意味着我们应该编写这样的服务:

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
}

这样,无论是否使用DI工具,您的服务都可以正常工作。


-1

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;

}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.