节点模块化架构

时间:2016-01-20 08:32:39

标签: node.js architecture npm

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

问题是:

  1. 有嵌套依赖,所以如果我更改模块A而模块B依赖于模块A而模块C依赖于模块B,那么当我更新模块AI时需要发布它的新版本,这意味着我需要更新它在模块B中,这意味着我还需要发布它,然后最后我需要在模块A中安装新版本...你可以看到可能是一个痛苦的地方。更重要的是,所有package.json中的版本更新都是手动的,因此容易出错,等待每次发布都很耗时。
  2. 模块可以共享npm依赖项,因此有时会在更新包时发生冲突。模块越多,冲突的可能性就越大。
  3. 好处是我们有一个非常模块化的系统,可以轻松地重复使用库,并且由于不存在任何循环依赖,因此可以实施明确的模块层次结构。

    可能的解决方案是:

    1. Monolith - 在单个存储库中将依赖关系作为单个应用程序进行管理,每个模块只是成为一项服务。这意味着只需要进行一次更新,并且所有模块apis都将同步。但是,引用代码中的库可能有点痛苦(因为我相信它们必须相对于本地文件被引用),我不确定如何强制执行模块之间的结构层次结构并且代码重用将会对存储库外的模块更加努力。

    2. 微服务 - 使每个模块成为微服务。这保留了模块化系统的所有好处,但我担心它会给构建增加很多复杂性,并且管理所有服务本身就会成为一项全职工作。

    3. 继续前进 - 找出一种方法来保持当前的架构,但不会出现推送更新等问题。也许脚本可以更新版本和shrinkwrap以确保正确的依赖关系。我认为这既困难又可能导致它成为一个不同种类的单一系统。

    4. 选项1对我来说似乎最容易管理,但如果我不需要,我不想失去模块化结构。

      这是一个相当广泛的问题,但任何建议/意见/评论都会非常有用。

      由于

3 个答案:

答案 0 :(得分:4)

我建议选择解决方案2.

  • 在小模块中细分所有代码。
  • 使用事件发射器实现松散耦合。
  • 将每个模块存储为自己的npm包没有附加值,除非它们在您的应用程序之外独立使用。

您描述的两个问题仅仅是由于每个模块都独立存储为npm包这一事实。

优势

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

示例实现

几个月前,我使用这些原则重构了一个单一的node.js应用程序,它确实简化了维护,并没有增加构建过程的开销。

模式如下:

主要模块是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

答案 1 :(得分:0)

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

您的软件包C的当前结构取决于软件包B,具体取决于软件包A导致软件包管理方面的困难,因为您需要对较低级别的软件包进行过多修改。

diagram of the current structure

这类问题是前期封装设计过多的标志,而封装设计确实应该随时进行。

您当前的结构的优点是它没有任何acylic 依赖。如果更新模块B,则确切知道模块C受到影响, 模块A没有任何变化。它应该保持这种状态。

管理依赖关系

依赖结构

包装设计原则与您遇到的问题直接相关:

<强> The Stable Dependencies Principle
取决于稳定性方向

鉴于原始结构C -> B -> A,您的大多数更改都应该在C中进行,而A不应该有任何改变的理由。

<强> The Stable-Abstractions Principle
一个包应该是稳定的抽象

与先前的原则有关。抽象类省略了特定的实现,有许多方法可以使用Javascript。

如果您遵循这些原则,您可能会发现这不是问题 拥有三个以上的套餐,因为较低级别的套餐不会发生太大变化。

包裹中应包含什么内容?

按功能打包

如今流行的MVC框架有一个将控制器,模型和视图分成不同文件夹的结构。这种结构不能很好地扩展,并且在项目扩展一段时间后,很难看到项目的作用,不同部分之间的连接方式,以及看到所有的不太方便。与特定功能相关的文件。这种名为逐层的方法不能很好地扩展。

组织包裹的更好方法是Package by Feature。现在分层并不是一件坏事,当你按功能打包时,你应该仍然拥有这种分层结构。

答案 2 :(得分:0)

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

模块化原则列表

  1. 代码按功能在文件夹中进行物理组织。
  2. 代码只做一件事,而且做得很好。
  3. 可以随时添加或删除每个功能,因为它与其他功能没有依赖关系。
  4. 与其他模块的所有通信都不是直接进行的。而是使用Sandbox或中间件。
  5. 如果有几个模块需要一个共同的功能,那么它们就是从一个上层的半不可变()结构中获取它。
  6. 优点和缺点 这种方法寻找一个特定的目标:松耦合。背后的想法是每个模块都可以单独实现,可以单独开发和测试,许多人可以同时贡献功能。 看看WordPress或节点生态系统。以此示例为例,将其移至项目中。

    一个例子

    CSS对我来说是一个明显的例子,说明这种模块化方法是如何工作的。如果您的网站包含许多页面,并且其中包含许多部分,并且您的客户希望每个部分都有外观的变体,那么最终可能会在一个缩小的大CSS文件中使用几百个CSS定义。

    CSS的治理可能需要变量,预处理器,PostCSS,Javascript处理......但实际情况是你不会在每个页面上使用多个CSS定义。

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

    文件夹结构建议 我的想法是所有代码都应该以相同的方式组织。 主文件夹:

    • core
    • extensions
    • 发动机

    在每个像这样的结构里面: 的
    -----推广经理
    ------------ css
    ------------ img
    ------------媒体
    ------------ js
    ------------查看
    ------------ db
    ------------数据
    ------------ aux
    ------------ config.json
    ------------ README.txt
    ------------ settings.txt
    -----沙箱
    ------------ css
    ------------ img
    ------------媒体
    ------------ js
    ------------查看
    ------------ db
    ------------数据
    ------------ aux ------------ config.json
    ------------ README.txt
    ------------ settings.txt
    -----全球
    ------------ css
    ------------ img
    ------------媒体
    ------------ js
    ------------查看
    ------------ db
    ------------数据
    ------------ aux
    ------------ config.json
    ------------ README.txt
    ------------ settings.txt
    扩展
    -----约会
    ------------ css
    ------------ img
    ------------媒体
    ------------ js
    ------------查看
    ------------ db
    ------------数据
    ------------ aux
    ------------ config.json
    ------------ README.txt
    ------------ settings.txt
    -----日历
    ------------ css
    ------------ img
    ------------媒体
    ------------ js
    ------------查看
    ------------ db
    ------------数据
    ------------ aux
    ------------ config.json
    ------------ README.txt
    ------------ settings.txt
    -----促销
    ------------ css
    ------------ img
    ------------媒体
    ------------ js
    ------------查看
    ------------ db
    ------------数据
    ------------ aux
    ------------ config.json
    ------------ README.txt
    ------------ settings.txt

    希望这些想法可能会有所帮助,欢迎提出任何意见。