【问题标题】:What is the best way to pass common variables into separate modules in Node.js?将公共变量传递到 Node.js 中的单独模块的最佳方法是什么?
【发布时间】:2012-05-05 13:52:31
【问题描述】:

我使用单独的路由器文件作为主应用程序和身份验证应用程序的模块。我无法获得将变量(数据库客户端)传递到路由器的最佳方法。我不想硬编码或传递它:

module.exports = function(app, db) {

也许使用单例寄存器或使用全局 db 变量是最好的方法?

您对设计模式有何经验?哪种方式最好,为什么?

【问题讨论】:

标签: design-patterns node.js


【解决方案1】:

如果您使用依赖注入框架,您可以节省所有用于连接模块的样板代码

This answer 列出了其中的一些。我还建了一个simpler DI framework here

编辑:以下是页面更改时的答案副本


require 是 Node.js 中管理依赖项的方式,它确实直观且有效,但也有其局限性。

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

仅举几例。

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

优点:

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

缺点:

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

【讨论】:

    【解决方案2】:

    完全过时了,但你可以使用global 在脚本中:

     global.foo = new Foo();
    

    在另一个脚本中:

     foo.bar();
    

    你也可以使用已经存在的常量:

     Object.foo = new Foo();
    

    这里:

     Object.foo.bar();
    

    【讨论】:

      【解决方案3】:

      我发现使用依赖注入来传递东西是最好的风格。它确实看起来像你有的东西:

      // App.js
      module.exports = function App() {
      };
      
      // Database.js
      module.exports = function Database(configuration) {
      };
      
      // Routes.js
      module.exports = function Routes(app, database) {
      };
      
      // server.js: composition root
      var App = require("./App");
      var Database = require("./Database");
      var Routes = require("./Routes");
      var dbConfig = require("./dbconfig.json");
      
      var app = new App();
      var database = new Database(dbConfig);
      var routes = new Routes(app, database);
      
      // Use routes.
      

      这有很多好处:

      • 它迫使您将系统分成具有明确依赖关系的组件,而不是将依赖关系隐藏在文件中间的某个位置,它们调用 require("databaseSingleton") 或更糟的是 global.database
      • 它使单元测试变得非常容易:如果我想单独测试Routes,我可以使用伪造的appdatabase 参数注入它并只测试Routes 代码本身。
      • 它将所有对象图连接放在一个地方,即组合根(在本例中为server.js,应用程序入口点)。这使您可以在一个地方查看所有内容如何在系统中组合在一起。

      我见过的对此更好的解释之一是an interview with Mark Seeman,他是优秀著作.NET 中的依赖注入 的作者。它同样适用于 JavaScript,尤其是 Node.js:require 通常用作经典的服务定位器,而不仅仅是模块系统。

      【讨论】:

      • 我正在尝试使用这个例子,但是,我如何在你的 Routes.js 实现中定义我的路由,因为它在这个 Routes 函数中?谢谢!
      • @cpeele00 和其他类的方法一样。
      • 我也喜欢这种风格。我构建了Electrolyte,它正是这样做的,同时消除了在整个应用程序中传递依赖项所需的手动管道。
      • 为了为整个应用程序保留一个单例,执行 function App(){...} module.exports = new App(); 更好吗?如果你这样做,你只是在缓存新的 App() 而不是函数,所以你可以确保 require() 缓存将使你的 obj 保持整个应用程序的单例。见:bites.goodeggs.com/posts/export-this/#singleton
      • @alph486 与依赖注入相反,我不推荐。单例通常是一种反模式。
      【解决方案4】:

      我建议您使用 db 实例以及您需要全局使用的其他内容(例如“singleton”)创建一个设置文件。

      例如,我的 redis db 客户端有 settings.js:

      var redis = require('redis');
      exports.redis = redis.createClient(6379, '127.0.0.1');
      

      我在其他多个模块中包含它:

      var settings = require('./settings');
      setting.redis.<...>
      

      很多时候包括它我总是有一个数据库连接实例。

      【讨论】:

      • 如果我使用下一个代码:` var redis = require('redis'); var client = redis.createClient(6379, '127.0.0.1'); client.auth(redispass);出口.客户=客户; ` 这个模块会一次又一次地重新连接redis吗?
      • 不,只有一个连接。找documentation多次调用require('foo')可能不会导致模块代码被多次执行
      • 依赖“require”来实现单例行为是有风险的,因为如果你通过完全相同的路径,你只会得到相同的实例。模块基于传递的路径进行缓存,而不是在解析的路径上。换句话说,它将作为单例工作,直到您尝试从子目录中使用它。
      • @JollyRoger 这不是真的。模块根据其解析的文件名进行缓存。见nodejs.org/api/modules.html#modules_module_caching_caveats
      • @Ilya 好的,在技术上是正确的 - 但它是相对于调用模块的解析文件名,如果你从子目录中执行它(根据我的评论)会有所不同。所以,我坚持我的“风险”评估。
      猜你喜欢
      • 1970-01-01
      • 2018-09-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-04-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多