4

微模块-前端业务模块化探索,拆解巨石应用的又一利器 - hiisea

 1 year ago
source link: https://www.cnblogs.com/hiisea/p/16624472.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

大家好,我是Eluxjs的作者,Eluxjs是一套基于“微模块”和“模型驱动”的跨平台、跨框架『同构方案』,欢迎了解...

文前声明,以下推断和结论纯属个人探索,鉴于本人知识水平所限,谬误在所难免,恳请各位大佬不吝赐教...

什么是前端“微模块”?

Elux中的『微模块』是指在Web前端工程中,将代码和相关资源按照不同的业务功能进行归类和模块化。

根据业务功能进行模块化一直以来都是后端的普遍做法,而Web前端则通常都是按照UI界面的视图区块View来进行模块化,这样的模块实际上只是Component组件,不具备独立自治的能力。究其原因我想是因为在早期Web1.0的时代,前端的职能就是仅仅作为后端API数据的一个Render渲染器,所以前后端的视野和格局出现了分化,也导致很多人说前端根本无架构之说。

然而web生态发展到今天,浏览器越来越强大,赋能越来越多,甚至不亚于一个小型操作系统,这时候的Web前端早已不是当初简单的数据渲染器,状态管理、会话维持、数据持久化、文件缓存、通信协议...随着PWA、小程序、快应用的推广,WebAPP不再是瘦客户端,渐渐成长为大胖小子。

此时的我们应当跳出“渲染器”的井口,而从一个完整的软件工程来思考我们的前端架构,Web前端不只是一层View、一个GUI,我们需要回归到与后端一致的以业务领域为驱动的模块化视角。

micro-module.png

为什么前端需要“微模块”?

  • 从开发角度来说:我们需要高内聚、低耦合的松散结构体,而不是牵一发而动全身的巨石应用,这不管是对于开发、维护、还是后期渐进式重构,都至关重要。

    前端Leader:经过一年多的迭代和人员变动,我们代码已经混乱不堪了,开发越来越吃力,必须要重构,否则玩不下去了!
    产品经理:嗯,我理解,这里面也有很多是我们需求变更频繁引起的,我支持你们重构!
    前端Leader:感谢大佬理解,那新需求先停下来,等我们重构好了再迭代吧?
    产品经理:你们重构要多久?
    前端Leader:产品这么复杂了,估计至少要3个月左右吧。
    产品经理被吓出一身冷汗:大佬,你要3天还可以考虑,停下来3个月估计公司都要关门了...
    前端Leader:可是产品这么复杂,几天时间完成重构是天方夜谭。
    产品经理想了想:这样把,我每个迭代少安排几个需求,这样你们每个月就可以留几天时间重构了。
    前端Leader:这可不是1+1=2的问题,而是0与1的问题,大佬你不了解!
    产品经理:谁说我不了解,你们就不能渐进式重构吗?
    前端Leader:...
    

    此时如果我们的前端工程是基于“微模块”,一来可以轻松的找到“局部重构”的边界,二来也可以通过维持“微模块”的对外接口来无极替换。

  • 从产品角度来说:软件架构永远是服务于业务需求的。我们希望我们的产品能像搭积木一样按需组合,可以快速包装出各种灵活多样的套餐,以满足客户越来越精细化的定制需求。

    某个大型应用包含A,B,C,D,E,F,G等若干功能,原来一直是整体打包出售...
    
    随着用户需求的多样化,有的用户仅需要部分功能,于是聪明的前端架构师“小李”利用时下流行的微前端技术,
    将应用拆分成了的 3 个子应用:
    
    - 【基础应用】包含功能:A
    - 【子应用A】包含功能:B,C,D
    - 【子应用B】包含功能:E,F,G
    
    这样等于有 3 个套餐可以供客户选择:
    
    - 套餐A:基础应用 + 子应用A
    - 套餐B:基础应用 + 子应用B
    - 套餐C:基础应用 + 子应用A + 子应用B
    
    然而用户的需求越来越精细化,有的需要ABCD,有的需要ACEG,有的需要ABDF...
    而且同一个功能可能还存在需求版本的不同,这让“小李”无可适从。
    

    现在我们利用“微模块”来帮助小李解决问题:

    • 将各种独立的业务功能封装成不同的微模块:A,B,C,D,E,F,G
    • 将各种微模块按需求迭代版本,发布成NPM包
    • 某客户需要 A,C1(C功能的某个版本),E2(E功能的某个版本),G 功能,我们单独为该客户创建一个聚合工程分支,安装相应版本的微模块:npm install A C@1 E@2 G
building-blocks.jpeg

我们知道世界上有一款建站神器wordpress,曾经号称世界上50%的网站都是由它创建的,我认为它的成功秘诀就是社区模版机制和功能插件化,你要什么功能都总能找到“前端+后端”一起打包安装的插件,这也类似于“微模块”的概念。

  • 从工程的角度来说:“微模块”是跨工程、跨项目共享通用业务代码的理想决方案,对于跨端、跨平台复用业务逻辑尤其有用。
micro-share.png

前端“微模块”的划分原则与边界

  • 拥有高内聚、低耦合的工程结构。
  • 拥有独立自治的子域逻辑。
micro-domain.png

从图中可以看到,每个微模块负责定义和维护自己领域内的事务,并且麻雀虽小,五脏俱全,拥有独立的路由解析、状态管理、数据模型、控制器、视图、组件、资源、业务实体、API管理等等...总之,所有与自己领域相关的资源都被内聚到了一起。

以下是某巨石应用的SRC目录,其特点是以“文件职能”作为一级分类、“功能模块”作为次级分类:

├─ src
│  ├─ api                 # API接口管理
│  ├─ assets              # 静态资源文件
│  ├─ components          # 全局组件
│  ├─ config              # 全局配置项
│  ├─ enums               # 项目枚举
│  ├─ hooks               # 常用 Hooks
│  ├─ language            # 语言国际化
│  ├─ layout              # 框架布局
│  ├─ routers             # 路由管理
│  ├─ store               # store
│  ├─ styles              # 全局样式
│  ├─ typings             # 全局 ts 声明
│  ├─ utils               # 工具库
│  ├─ views               # 项目所有页面
│  ├─ App.vue             # 入口页面
│  └─ main.ts             # 入口文件

以下是Elux中基于微模块的SRC目录,其改进是将“功能模块”作为一级分类,“文件职能”作为次级分类:

src
├── modules
│      ├──  ModuleA
│      │     ├── entities
│      │     ├── assets
│      │     ├── api
│      │     ├── utils
│      │     ├── language
│      │     ├── components
│      │     ├── views
│      │     ├── model.ts
│      │     └── index.ts
│      │ 
│      ├── ModuleB
│      ├── ModuleC

微模块的台前与幕后

前端开发最终呈现的是UI界面,但这只是表象,支撑UI界面渲染和交互的是背后一系列state、model、controller等幕后英雄,它们根据自己所属不同领域被封装在各个微模块中,UI既然与它们唇齿相依,必然也将跟随它们内聚在一起。

View和Component

本质上说View就是一个Component,但我们从架构的思维来区分它们:

  • View:业务视图,它用来表现业务规则与逻辑,通常能够较为独立和完整的解决某一领域问题。
  • Component:UI组件,它用来表现渲染规则与交互逻辑,通常不与具体业务直接相关,可复用在各种不同业务场景中。

所以在“微模块”的架构中,丰富多彩的UI界面由一个个单一职责的View聚合而成,每个View同样依据自身所解决的领域问题而被分散在各个微模块中,这里面有几个注意点:

  • 领域性:View被归属到不同微模块的原则是其解决的问题领域,而不是视觉上的几何空间。View可以在视觉上被拆装、聚合、嵌套,这并不影响它们所属微模块。
  • 完整性:一个View通常能解决一个较为独立和完整的问题,View与View之间是较为松散的关系,如果2个View之间联系紧密,那就不应当拆分它们。
micro-view.png

不以视觉延伸和几何空间作为View的微模块归属原则:如下图所示,假设有一个View用来展示用户资料,我们将其放在UserModule这个微模块中,称其为UserModule.DetailView,但你发现其中又包含一个该用户发表文章的列表,你当然可以把这个列表单独提取出来作为一个新的View。从视觉上来看,它似乎和用户资料是连在一起的,似乎可以和UserModule.DetailView放在同一个微模块中;但我们从它解决的问题来看,它属于文章领域,而与用户领域关系并不大,所以我们最好将其放在ArticleModule中,称其为ArticleModule.ListView

micro-view2.png

前端“微模块”的实现方案

  1. 定义和创建微模块,可借助于Eluxjs框架,当然你发现了其它框架也可以。
  2. 管理微模块,可借助于NPM仓库。
  3. 使用微模块,可借助于打包工具:
    • 静态编译:微模块作为一个NPM包被安装到工程中,通过打包工具(如webpack)正常编译打包即可。这种方式的优点是代码产物得到打包工具的各种去重和优化;缺点是当某个模块更新时,需要整体重新打包。
    • 动态注入:利用Module Federation,将微模块作为子应用独立部署,与时下流行的微前端类似。这种方式的优点是某子应用中的微模块更新时,依赖该微模块的其它应用无需重新编译,刷新浏览器即可动态获取最新模块;缺点是没有打包工具的整体编译与优化,代码和资源容易重复加载或冲突。
micro-install.png

微模块 vs 微前端

从本意上来说,微模块只是一种工程结构和模块化方案,而微前端只是它的一种应用场景之一。微模块架构不仅可以用来构建复杂的单体应用,也可以结合Module Federation实现多子应用独立部署的“微前端”。

如果单独就微模块 + ModuleFederation方式实现的微前端,与传统意义上的qiankun、icestark等微前端方案相比,微模块方式胜在粒度更细、更灵活、更轻巧,而传统方式则胜在隔离性更好。

想到一个非常形象的比喻:

IFrame vs 微前端 vs 微模块 可类比于 进程 vs 线程 vs 协程

从左至右:越来越轻量化,隔离性逐渐变弱,灵活性逐渐增加。所以鱼与熊掌不可兼得,具体哪种方案最适合还得看不同的产品需求。

微模块之间的通信

  • 微模块之间按照某些规则和约定共享同一个Runtime,强制隔离性较弱,所以它们之间的通信是轻量级的,可以相互引用与调用。
  • 建议观察者模式,或者使用事件总线模式来保持微模块之间的松散关系,这是另一个故事,可参考Eluxjs中的ActionBus
  • 微模块高内聚、低耦合的划分原则,也意味着微模块之间不会出现特别复杂的互动与交流(互动密切的微模块应当合并)。

落地与实战

光练不说傻把式,光说不练假把式,这里先把思路概念要说的说完,下面就要开始出实例了。先喝口水,请听下回分解...🤩🤩急性子也可以直接去Eluxjs官网,看看Demo,不吝赐教...


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK