节点模块化架构

我正在构build一个相当大的nodejs应用程序。 为了避免一个单一的节点应用程序,我已经走下了一个更模块化系统的架构路线,将几个组件分解成单独的npm模块。 这些使用npm发布并安装在依赖模块中。 我有大约6个不同的模块(我想要更多的),现在pipe理软件包变得困难了。

问题是:

  1. 有嵌套的依赖关系,所以如果我改变模块A和模块B依赖于模块A和模块C依赖于模块B,那么当我更新模块AI需要发布它的新版本,这意味着我需要更新它在模块B,这意味着我也需要发布,然后最后我需要在模块A中安装新版本…你可以看到哪里可能是一个痛苦。 更重要的是,所有package.json中的版本更新都是手动的,所以容易出错,并且等待每个发布是耗时的。
  2. 模块可以共享npm依赖关系,所以有时包更新时会发生冲突。 越多的模块冲突的机会越高。

好处是我们有一个非常模块化的系统,在这个系统中,库可以很容易地被重用,并且有一个明确的模块层次结构,因为不能有循环依赖。

可能的解决scheme

  1. Monolith – 将每个模块的依赖关系作为一个单一的应用程序来pipe理,每个模块只是成为一个服务。 这意味着只有一个更新是必要的,所有的模块apis将同步。 然而,在代码中引用库可能有点痛苦(因为我相信它们将不得不被相对于本地文件引用),我不确定模块之间的结构层次结构如何被强制执行并且代码重用将会使用仓库以外的模块会更困难。

  2. 微服务 – 使每个模块成为微服务。 这保持了模块化系统的所有好处,但是我担心它会增加构build的复杂性,并且pipe理所有的服务将成为一个全职的工作。

  3. 继续 – 制定一种方法来保持当前的体系结构,但消除推送更新等问题。也许脚本来更新版本和缩小包装,以确保正确的依赖关系。 我认为这将是困难的,并有可能导致它成为一个不同的整体系统。

选项1对我来说似乎是最易于pipe理的,但是如果我不需要,我不想失去模块化结构。

这是一个相当广泛的问题,但任何build议/build议/意见将是非常有帮助的。

谢谢

我build议去解决scheme2。

  • 用小模块分解所有的代码。
  • 与事件发射器实现松耦合。
  • 除非将它们单独用于应用程序以外,否则将每个模块作为自己的npm包存储都没有附加价值。

您所描述的两个问题仅仅是由于每个模块独立存储为npm包而造成的。

优点

  • 问题1已经解决了,因为您不需要再pipe理package.json npm包了。
  • 问题2解决了,因为你只有一个package.jsonpipe理所有的依赖关系
  • 由于使用了独立的node.js模块,您仍然拥有干净的模块化结构。

示例实现

几个月前,我使用这些原则重构了一个单一的node.js应用程序,它确实减轻了维护工作,并且不会增加构build过程的开销。

模式如下:

主要模块是app.js

 var sys = require('sys') , events = require('events') , UserModel = require('./model/user') // this is a microservice taking care of managing user data , Logic = require('./controller/logic') // this is another microservice doing some work var App = function (server, app) { this.controller = ( logic: new Logic(this) // "this" is the app instance, it's passed to all microservices, which will benefit from the access to event emitter... } this.model = { new UserModel(this) } // ... } sys.inherits(App, events.EventEmitter) module.exports = App 

微服务如下所示:

 /** * Logic functions * * this controller does ... * * @constructor * */ function Logic(app) { /**************************************** * * Private methods * ****************************************/ /** * this is a private function in the controller * * @private */ function computeSomething () { var res = 3 app.emit('computed', res) // emit event, that can be liseted to by some other modules return res } /**************************************** * * Public methods * ****************************************/ /** * * This function can be called directly by the other modules using "app.controller.logic.work()" * */ this.work = function () { return 'result is ' + computeSomething() } /**************************************** * * Event listeners * ****************************************/ /** * listener: event from the app - loose-coupling magic happens thanks to this. Recommended over public functions. * * @private */ app.on('data-ready', this.work) } module.exports = Logic 

你考虑过不同的模块化结构吗? 微服务或巨石的决定会影响组件之间的相互通信,系统的可扩展性和可部署性,但是您仍然需要遵循包devise的最佳实践。 否则,在更新一个低级别的包时,你会有相同的连锁反应。

由于您需要对较低级别的软件包进行太多的修改,因此,根据程序包A取决于程序包B的当前程序包C的结构会导致程序包pipe理困难。

当前结构图

这种types的问题是过多的前期包装devise的标志,而包装devise的确应该按照你的要求去做。

你现在的结构的优点是它没有任何的依赖关系。 如果更新模块B,则确切地知道模块C受到影响,模块A没有任何更改。它应该保持这种状态。

pipe理依赖关系

依赖结构

有一些包装devise原则直接关系到你所拥有的问题:

稳定的依赖性原则
依靠稳定的方向

给定原始结构C -> B -> A ,你的大部分改变应该发生在CA不应该有任何理由改变。

稳定抽象原则
包应该是抽象的,因为它是稳定的

关于以前的原则。 抽象类忽略了具体的实现,并且有很多方法可以用Javascript来实现。

如果你遵循这些原则,你可能会发现有三个以上的软件包不是问题,因为较低级别的软件包不会有太大的改变。


包里应该包含什么?

按function打包

如今stream行的MVC框架具有将控制器,模型和视图分离到不同文件夹的结构。 这个结构不能很好地扩展,在项目扩展了一段时间之后,很难直观地看到项目做了什么,不同的部分是如何相互连接的,而且看到所有与a相关的文件并不是很方便特别的function。 这种逐层调用的方法不能很好地扩展。

按照function打包,组织软件包的一种更好的方法是打包 。 现在分层并不是一件坏事,当你按function打包时,你仍然应该有这种分层结构。

我正在研究一个理论模型来解决这个问题,只做一些研究,一点实验和一些常识。

模块化原则列表

  1. 该代码是按function物理组织在文件夹中的。
  2. 代码只做一件事,而且做得很好。
  3. 每个function都可以随时添加或删除,因为它与其他function无关。
  4. 所有与其他模块的通信都不是直接进行的。 使用沙盒或中间件代替。
  5. 如果几个模块需要一个共同的function,他们从一个较高层次semi-immutable()结构。

优点和缺点这种方法寻找一个特定的目标:松耦合。 其背后的思想是每个模块都可以独立实现,可以单独开发和testing,很多人可以同时提供特性。 看看WordPress,还是节点生态系统。 拿这个例子,把它移到你的项目。

一个例子

对于我来说,CSS是一个很好的例子,这个模块化的方法可能会起作用。 如果你的站点有很多页面,而且你的客户希望每一个部分都有不同的外观和感觉,那么你最终可能会在一个小型的CSS文件中得到几百个CSS定义。

CSS的pipe理可能需要variables,预处理器,PostCSS,Javascript处理….但真正的问题是,你将永远不会在每个页面上使用超过几个CSS定义。

如果所有的页面都可以分成模块types,每种types都有其自己的规则,那么最终可能会得到更多的代码,但是当时只能应用一个较小的文件。 也许你不需要缩小任何文件,因为所有的都有需要。

文件夹结构build议我正在工作的想法,所有的代码应该组织相同的方式。 主要文件夹:

  • 核心
  • 扩展
  • 引擎

在每个像这样的结构里面: 核心
—–扩展pipe理器
———— css
———— img
媒体
———— js
————视图
– – – – – – D b
————数据
———— aux
———— config.json
———— README.txt
———— settings.txt
—–沙箱
———— css
———— img
媒体
———— js
————视图
– – – – – – D b
————数据
———— aux ———— config.json
———— README.txt
———— settings.txt
—–全球
———— css
———— img
媒体
———— js
————视图
– – – – – – D b
————数据
———— aux
———— config.json
———— README.txt
———— settings.txt
扩展
—–约会
———— css
———— img
媒体
———— js
————视图
– – – – – – D b
————数据
———— aux
———— config.json
———— README.txt
———— settings.txt
—–日历
———— css
———— img
媒体
———— js
————视图
– – – – – – D b
————数据
———— aux
———— config.json
———— README.txt
———— settings.txt
—–促销
———— css
———— img
媒体
———— js
————视图
– – – – – – D b
————数据
———— aux
———— config.json
———— README.txt
———— settings.txt

希望这些想法可以帮助,任何意见,欢迎。