我是否需要NodeJS中的依赖注入,或者如何处理...?

时间:2012-02-12 17:19:47

标签: node.js dependency-injection inversion-of-control

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

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

我说的是简单的事情,比如分享数据库连接对象,到目前为止,但我还没有找到满足我的解决方案。

22 个答案:

答案 0 :(得分:95)

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

JS的优点在于你可以修改任何东西来实现你想要的东西。这在测试时非常方便。

看看我非常蹩脚的例子。

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模式来管理您的依赖项。希望这会有所帮助。

答案 1 :(得分:68)

require 在Node.js中管理依赖关系的方式,当然它是直观有效的,但它也有其局限性。

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

仅举几例。

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

<强>优点:

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

<强>缺点:

  • 与Node.js“体验”不同:不使用require肯定会让您感觉偏离了Node的思维方式。
  • 依赖项与其实现之间的关系并不总是明确的。可以在运行时解决依赖性并且受各种参数的影响。代码变得更难理解和调试
  • 启动时间较慢
  • 成熟度(目前):目前没有一种解决方案非常受欢迎,所以没有那么多教程,没有生态系统,没有经过战斗测试。
  • 某些DI容器无法与Browserify和Webpack等模块捆绑器配合使用。

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

答案 2 :(得分:39)

我知道这个帖子在这一点上已经很老了,但我想我会对这个想法充满信心。 TL; DR是由于JavaScript的无类型动态特性,实际上可以做很多事情而不依赖于依赖注入(DI)模式或使用DI框架。但是,随着应用程序变得越来越大,越来越复杂,DI肯定有助于代码的可维护性。

C#

中的DI

要理解为什么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();
    }
}

以这种方式编写代码几乎没有问题。

  1. Car类与Horn类中的号角的特定实现紧密耦合。如果我们想要改变汽车使用的喇叭类型,我们必须修改Car级别,即使喇叭的使用没有改变。这也使得测试变得困难,因为我们不能将Car类与其依赖性Horn类隔离开来进行测试。
  2. Car类负责Horn类的生命周期。在一个这样的简单示例中,它不是一个大问题,但在实际应用程序中,依赖项将具有依赖项,这将具有依赖项等。Car类需要负责创建整个树。它的依赖关系。这不仅复杂而且重复,而且违反了单一责任&#34;班上的。它应该专注于成为汽车,而不是创造实例。
  3. 无法重用相同的依赖项实例。同样,这在这个玩具应用程序中并不重要,但考虑数据库连接。您通常会在应用程序中共享一个实例。
  4. 现在,让我们重构一下,使用依赖注入模式。

    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!");
        }
    }
    

    主要可以只注入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模块将尝试在honk模块导出的任何内容上调用horn方法。

    此外,由于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 Framework

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

    我对任何可用的选项都不是特别满意所以我写了自己的bolus。 Bolus旨在使用上面的DI风格编写的代码,并试图非常简单DRY。使用上面完全相同的car.jshorn.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支持一系列漂亮的功能,如可选的依赖项和测试全局变量,但我相对于标准的Node.js方法有两个主要的好处。首先,如果你有很多类似的应用程序,你可以为你的基础创建一个私有的npm模块,它创建一个注入器并在其上注册有用的对象。然后,您的特定应用可以根据需要添加,覆盖和解决,就像AngularJS's注入器的工作方式一样。其次,您可以使用bolus来管理各种依赖关系的上下文。例如,您可以使用中间件为每个请求创建子注入器,在注入器上注册用户ID,会话ID,记录器等以及任何模块,具体取决于那些。然后解决您提供请求所需的内容。这为您提供了每个请求的模块实例,并且可以防止必须将记录器等传递给每个模块函数调用。

答案 3 :(得分:37)

我还写了一个模块来实现这个目标,它被称为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's injectr的启发,但采用了不同的方法。我不使用vm来评估测试模块,实际上我使用节点自己的需求。这样,您的模块就像使用require()一样(除了您的修改)。完全支持调试。

答案 4 :(得分: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' ];

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

创建数据库连接:

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

Electrolyte传递遍历@require'd依赖项,并将实例作为参数注入导出的函数。

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

当运行完整的应用程序时,Electrolyte会在模块间级别进行操作,将所有内容连接在一起,而无需使用全局,单例或过多的管道。

答案 5 :(得分:8)

我自己调查了一下。我不喜欢引入魔法依赖工具库,它提供了劫持我的模块导入的机制。相反,我提出了一个&#34;设计指南&#34;让我的团队通过在我的模块中引入工厂函数导出来明确说明可以模拟哪些依赖项。

我广泛使用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语法。

答案 6 :(得分:5)

我最近检查过这个帖子的原因与OP大致相同 - 我遇到的大多数lib都会暂时重写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');

如果加载器中没有别名 - 那么它将默认为常规需求。 这有两个好处:我可以交换任何版本的类,它消除了需要 在整个应用程序中使用相对路径名(所以如果我需要下面的自定义库 或者在当前文件之上,我不需要遍历,并且要求将对相同的密钥缓存模块)。它还允许我在应用程序的任何位置指定模拟,而不是在直接测试套件中。

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

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

答案 7 :(得分: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容器的想法。您可以在文章The current state of dependency inversion in JavaScript中了解有关此想法的更多信息。本文试图揭穿一些&#34; JavaScript IoC容器神话&#34;:

  

误区1:JavaScript中没有IoC容器的位置

     

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

     

神话3:依赖性倒置===注入依赖性

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

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

您可以在InversifyJS了解详情。

答案 8 :(得分:2)

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

Spring Framework的启发,我还实现了自己的模块来支持Nodejs中的依赖注入。我的模块还能够在不重新启动您的应用程序的情况下检测code changesauto reload服务。

访问我的项目:Buncha - IoC container

谢谢!

答案 9 :(得分: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()

答案 10 :(得分:2)

我一直很喜欢IoC概念的简单性 - “你不需要了解任何有关环境的知识,在需要时你会被某人调用”

但是我看到的所有IoC实现恰恰相反 - 它们使代码更加混乱,而不是没有它。所以,我创建了自己的IoC,它可以像我一样工作 - 它保持隐藏,90%的时间不可见

它用于MonoJS网络框架http://monojs.org

  

我说的是简单的事情,比如共享数据库连接对象,所以   远,但我还没有找到满足我的解决方案。

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

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

并在任何地方使用

app.db.findSomething()

您可以在此处查看完整的组件定义代码(包含数据库连接和其他组件)https://github.com/sinizinairina/mono/blob/master/mono.coffee

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

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

答案 11 :(得分:1)

Google的di.js适用于nodejs(+浏览器)(+ ES6)

答案 12 :(得分:1)

这取决于您的应用程序的设计。你可以显然做一个类似java的注入,你创建一个类的对象,并在构造函数中传递依赖关系。

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

var cache = new Cache(mysqlStore);

如果你没有在javascript中进行OOP,你可以创建一个设置所有内容的初始化函数。

但是,您可以采用另一种方法,这种方法在基于事件的系统(如node.js)中更常见。如果你可以模拟你的应用程序(大部分时间)对事件进行操作,那么你需要做的就是设置所有东西(我通常通过调用init函数来完成)并从存根发出事件。这使得测试相当容易和可读。

答案 13 :(得分:1)

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

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

答案 14 :(得分:0)

我认为其他帖子在使用DI的论据中做得很好。对我来说原因是

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

  2. 它可以更轻松地模拟测试依赖项,而不会以一种无问题的方式覆盖全局require函数。

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

  4. 但是我很难找到我和我的团队可以轻松采用的DI框架。所以我最近built a framework called deppie基于这些功能

    • 可在几分钟内学会的最小API
    • 无需额外的代码/配置/注释
    • 一对一直接映射到require模块
    • 可部分采用现有代码

答案 15 :(得分:0)

它应该像这样灵活简单:

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


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

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

我希望它可以帮助你。

答案 16 :(得分:0)

Node.js需要DI和任何其他平台一样多。如果你正在构建一些大的东西,那么DI将更容易模拟代码的依赖关系并彻底测试你的代码。

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

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

<input type="number" min="10" max="20" />

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

还有其他解决方案(百老汇,建筑师等)可以帮助您解决这个问题。虽然它们可能比你想要的更多或使用更多的混乱。

答案 17 :(得分:0)

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

详细了解KlarkJS

简要示例:

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

答案 18 :(得分:0)

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

希望它有所帮助!

答案 19 :(得分:0)

我在自己的DI模块上answering to an issue发现了这个问题,询问为什么需要一个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工具。

答案 20 :(得分:0)

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

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

答案 21 :(得分:0)

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;

}