【问题标题】:Node Modular Architecture节点模块化架构
【发布时间】:2016-04-26 00:03:25
【问题描述】:

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

问题是:

  1. 存在嵌套依赖,所以如果我更改模块 A,模块 B 依赖模块 A,模块 C 依赖模块 B,那么当我更新模块时,AI 需要发布它的新版本,这意味着我需要更新它在模块 B 中,这意味着我还需要发布它,然后最后我需要在模块 A 中安装该新版本......你可以看到这可能是一个痛苦的地方。而且所有 package.json 中的版本更新都是手动的,容易出错,每次发布都需要等待。
  2. 模块可以共享 npm 依赖项,因此在更新包时有时会发生冲突。模块越多,冲突的可能性就越高。

好处是我们有一个非常模块化的系统,其中库可以很容易地重用,并且由于不能有任何循环依赖关系,所以执行的模块层次结构清晰。

可能的解决方案是:

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

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

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

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

这是一个相当广泛的问题,但任何建议/建议/cmets 都会非常有帮助。

谢谢

【问题讨论】:

  • 手动更新 package.json 似乎是个大问题,为什么不简单地做一个“moduleA”:“*”?然后,您可以确保在构建/部署脚本中执行 npm install。假设事情没有中断,在部署之前应该由您的持续集成服务器验证
  • 另外,如果您更新了 A 和 B,但希望 C 使用旧版本的 B。您可以使用 npm shrinkwrap。
  • 您目前使用的 npm 模块是否需要独立运行?
  • 不,它们不需要自己运行,但是在其他项目中使用,例如支持工具
  • @RahatMahbub 感谢您的建议,我们允许对配置进行细微更改,但仍需要重新运行每个构建以合并更新的模块

标签: node.js architecture npm


【解决方案1】:

我建议选择解决方案 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

【讨论】:

  • 谢谢,我正在考虑使用 http 消息传递的更精细的微服务结构,但这很好,因为它更易于管理并维护依赖关系
【解决方案2】:

您是否考虑过不同的模块化结构?选择微服务还是单体应用会影响组件之间的通信方式、系统的可扩展性和可部署性,但您仍然需要遵循包设计中的最佳实践。否则在更新低级包时会产生同样的连锁反应。

您当前的包 C 依赖包 B 依赖包 A 的结构导致包管理困难,因为您需要对较低级别的包进行太多修改。

这种类型的问题是过多的前期包装设计的标志,而包装设计确实应该随手完成。

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

管理依赖项

依赖结构

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

The Stable Dependencies Principle
取决于稳定的方向

鉴于C -> B -> A 的原始结构,您的大部分更改应该发生在CA 不应有任何更改的理由。

The Stable-Abstractions Principle
一个包应该是抽象的,因为它是稳定的

与前面的原理有关。抽象类省略了特定的实现,您可以通过多种方式使用 Javascript 来实现。

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


包裹里应该有什么?

按功能打包

如今如此流行的 MVC 框架具有将控制器、模型和视图分隔到不同文件夹中的结构。这种结构的扩展性不是很好,而且项目扩展了一段时间后,很难想象项目做了什么,不同的部分是如何相互连接的,而且查看所有与某个项目相关的文件也不是很方便特殊功能。这种称为逐层封装的方法扩展性不是很好。

一个更好的方式来组织你的包是Package by Feature。现在分层不是坏事,按功能打包的时候,应该还是有这种分层结构的。

【讨论】:

  • 所以请允许我换个说法/总结一下。代码库可能需要大量重构。 A 应该有所有相当恒定的代码。 B 应该有缓慢移动的代码,并且可能/可能不独立于 A。C 是包含不断发展的部分的模块/库。
  • 谢谢。我将尝试详细说明我的答案,因为它可能没有多大意义。
  • 感谢您的建议。我认为明智地划分到它自己的模块是一个很好的论据,但是有一些明确的领域我想保持不同,比如来自 api 的数据访问层,这两者都经常变化。我将不得不考虑如何采纳您的建议。
  • 一个更好的决定包中应该包含什么的原则是按功能打包,这主要与在您的项目中实现良好的内聚有关。我编辑了我的答案来写这个。另一个对管理特性之间的依赖关系有用的原则是Inversion of Control
【解决方案3】:

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

模块化原则列表

  1. 代码按功能物理组织在文件夹中。
  2. 代码做了一件事,而且只做一件事,而且做得非常好。
  3. 每个功能都可以随时添加或删除,因为它与其他功能没有依赖关系。
  4. 与其他模块的所有通信都不是直接进行的。而是使用沙盒或中间件。
  5. 如果多个模块需要一个通用功能,它们会从上层半不可变 () 结构中获取。

优点和缺点 这种方法寻找一个特定的目标:松散耦合。背后的想法是每个模块都可以独立实现,可以单独开发和测试,并且许多人可以同时贡献功能。 看看 WordPress 或节点生态系统。拿这个例子,把它移到你的项目中。

一个例子

CSS 对我来说是一个清楚的例子,说明了这种模块化方法是如何工作的。如果您的网站包含许多页面和多个部分,并且您的客户希望每个部分都具有不同的外观和感觉,那么您最终可能会在一个缩小的大 CSS 文件中包含数百个 CSS 定义。

该 CSS 的治理可能需要变量、预处理器、PostCSS、Javascript 处理....但真正的问题是您永远不会在每个页面上使用超过几个 CSS 定义。

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

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

  • 核心
  • 扩展
  • 引擎

在每个内部都有这样的结构: 核心
----- 扩展管理器
------------ css
------------ img
------------ 媒体
------------ js
------------ 查看
------------ 数据库
------------ 数据
------------ 辅助
------------ config.json
------------ README.txt
------------ settings.txt
----- 沙盒
------------ css
------------ img
------------ 媒体
------------ js
------------ 查看
------------ 数据库
------------ 数据
------------ 辅助 ------------ config.json
------------ README.txt
------------ settings.txt
----- 全球
------------ css
------------ img
------------ 媒体
------------ js
------------ 查看
------------ 数据库
------------ 数据
------------ 辅助
------------ config.json
------------ README.txt
------------ settings.txt
扩展
----- 约会
------------ css
------------ img
------------ 媒体
------------ js
------------ 查看
------------ 数据库
------------ 数据
------------ 辅助
------------ config.json
------------ README.txt
------------ settings.txt
----- 日历
------------ css
------------ img
------------ 媒体
------------ js
------------ 查看
------------ 数据库
------------ 数据
------------ 辅助
------------ config.json
------------ README.txt
------------ settings.txt
----- 促销活动
------------ css
------------ img
------------ 媒体
------------ js
------------ 查看
------------ 数据库
------------ 数据
------------ 辅助
------------ config.json
------------ README.txt
------------ settings.txt

希望这些想法能有所帮助,欢迎任何 cmets。

【讨论】:

    猜你喜欢
    • 2011-04-23
    • 1970-01-01
    • 1970-01-01
    • 2016-07-24
    • 2018-03-05
    • 2018-11-27
    • 2012-08-27
    • 2014-10-27
    • 1970-01-01
    相关资源
    最近更新 更多