首页 文章

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

提问于
浏览
170

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

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

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

21 回答

  • 5

    简而言之,您不需要像在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 时返回完全不同的模块 .

    Method 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;
    

    Method 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专家可能能够使用其他解决方案 .

    Update:

    这应该解决有关数据库连接的特定问题 . 我将创建一个单独的模块来封装数据库连接逻辑 . 像这样的东西:

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

  • 37

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

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

    仅举几个 .

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

    Pros:

    • 更好的可测试性:模块接受它们的依赖关系作为输入

    • 控制反转:决定如何在不触及应用程序主代码的情况下连接模块 .

    • 用于解析模块的可自定义算法:依赖项具有"virtual"标识符,通常它们不绑定到文件系统上的路径 .

    • 更好的可扩展性:由IoC和"virtual"标识符启用 .

    • 其他可能的花哨东西:

    • 异步初始化

    • 模块生命周期管理

    • DI容器本身的可扩展性

    • 可以轻松实现更高级别的抽象(例如AOP)

    Cons:

    • 与Node.js不同"experience":不使用 require 绝对感觉你偏离了Node的思维方式 .

    • 依赖项与其实现之间的关系并不总是明确的 . 可以在运行时解决依赖性并且受各种参数的影响 . 代码变得更难以理解和调试

    • 启动时间较慢

    • 成熟度(目前):当前流行的解决方案都没有受到欢迎,因此没有那么多教程,没有生态系统,没有经过战斗测试 .

    • 某些DI容器无法与Browserify和Webpack等模块捆绑器一起使用 .

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

  • 89

    我've also written a module to accomplish this, it'被称为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() 完全相同(除了您的修改) . 完全支持调试 .

  • 64

    我知道这个帖子在这一点上已经很老了,但我想我会想到这个问题 . 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();
        }
    }
    

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

    • Car 类紧密耦合到 Horn 类中喇叭的特定实现 . 如果我们想要改变汽车使用的喇叭类型,我们必须修改 Car 类,即使它的喇叭的使用没有单独测试 Car 类与它的依赖关系 Horn 类 .

    • Car 类负责 Horn 类的生命周期 . 在一个这样的简单示例中,它不是一个大问题,但在实际应用程序中,依赖项将具有依赖项,这将具有依赖项等. Car 类需要负责创建其依赖项的整个树 . 这不仅复杂而且重复,但它违反了该类的"single responsibility" . 它应该专注于成为一辆汽车,而不是创造实例 .

    • 无法重用相同的依赖项实例 . 同样,这在这个玩具应用程序中并不重要,但考虑数据库连接 . 您通常会在应用程序中共享一个实例 .

    现在,让我们重构一下,使用依赖注入模式 .

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

    我们've done two key things here. First, we'已经引入了我们的 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 模块将尝试在 horn 模块导出的任何内容上调用 honk 方法 .

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

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

    人们处理测试问题的常用方法是使用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 模块,您可以使用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, Logger 等以及任何模块,具体取决于那些 . 然后解决您需要提供请求的内容 . 这为您提供了每个请求的模块实例,并防止必须将 Logger 等传递给每个模块函数调用 .

  • 1

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

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

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

    module.exports = function (dep1, dep2) {
    // private methods
    
       return {
        // public methods
           test: function(){...}
       }
    }
    

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

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

  • 30

    我为此目的 Build 了Electrolyte . 那里的其他依赖注入解决方案对我的口味来说太过于侵略,而且对全球性的不满是我的特别不满 .

    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本身 . 这意味着您的单元测试可以只测试被测模块,传入模拟对象而无需额外的依赖关系来重新连接内部 .

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

  • 0

    我最近检查这个线程的原因与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');
    

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

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

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

  • 2

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

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

  • 0

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

    但是我看到的所有IoC实现都恰恰相反 - 它们使代码更加混乱,而不是没有它 . 所以,我创建了自己的IoC,它可以像我一样工作 - it stays hidden and invisible 90% of time .

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

    到目前为止,我正在谈论简单的事情,比如共享数据库连接对象,但我还没有找到满足我的解决方案 .

    它是这样完成的 - 注册组件配置一次 .

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

    并在任何地方使用它

    app.db.findSomething()
    

    您可以在此处查看完整的组件定义代码(使用DB Connection和其他组件)https://github.com/sinizinairina/mono/blob/master/mono.coffee

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

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

  • 0

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

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

    访问我的项目:Buncha - IoC container

    谢谢!

  • 0

    实际情况是,您可以在没有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中了解有关此想法的更多信息 . 文章试图揭穿一些"JavaScript IoC container myths":

    误区1:JavaScript中没有IoC容器的地方神话2:我们不需要IoC容器,我们已经有了模块加载器!神话3:依赖倒置===注入依赖关系

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

    • 内核模块

    • 内核中间件

    • 使用类,字符串文字或符号作为依赖项标识符

    • 注入常数值

    • 注入类构造函数

    • 注射工厂

    • 汽车厂

    • 注入提供者(异步工厂)

    • 激活处理程序(用于注入代理)

    • 多次注射

    • 标记绑定

    • 自定义标签装饰器

    • 命名绑定

    • 上下文绑定

    • 友好异常(例如循环依赖)

    您可以在InversifyJS了解更多相关信息 .

  • 0

    对于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,你可以创建一个设置一切的init函数 .

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

  • 5

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

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

  • 0

    谷歌的di.js适用于nodejs(浏览器)(ES6)

  • 2

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

    希望能帮助到你!

  • 16

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

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

    • 这使得模拟依赖项进行测试变得更加容易,而没有以一种无问题的方式覆盖全局 require 函数的痛苦 .

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

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

    • 可在几分钟内学会的最小API

    • 无需额外的代码/配置/注释

    • 一对一直接映射到 require 模块

    • 可以部分采用现有代码

  • 0

    它应该像这样灵活简单:

    var MyClass1 = function () {}
    var MyClass2 = function (myService1) {
        // myService1.should.be.instanceof(MyClass1); 
    }
    
    
    container.register('myService1', MyClass1);
    container.register('myService2', MyClass2, ['myService1']);
    

    我在node.js上写了关于依赖注入的文章 .

    我希望它可以帮助你 .

  • 2

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

    了解更多KlarkJS

    简要示例:

    KlarkModule(module, 'myModuleName1', function($nodeModule1, myModuleName2) {
        return {
            log: function() { console.log('Hello from module myModuleName1') }
        };
    });
    
    • myModuleName1 是模块的名称 .

    • $nodeModule1 是来自 node_module 的外部库 . 该名称解析为 node-module1 . 前缀 $ 表示它是外部模块 .

    • myModuleName2 是内部模块的名称 .

    • 控制器的返回值在定义参数 myModuleName1 时从其他内部模块使用 .

  • 2

    我在自己的DI模块上发现了这个问题answering to an issue,询问为什么需要一个用于NodeJS编程的DI系统 .

    答案显然倾向于这个帖子中给出的答案:这取决于 . 两种方法都存在权衡取舍,并且阅读这个问题的答案可以很好地解决这些问题 .

    所以,这个问题的真正答案应该是,在某些情况下,你会使用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

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

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

相关问题