構建高可伸縮性的WEB交互式系統(中)
在《構建高可伸縮性的WEB交互式系統》的第一篇,我們介紹了Web交互式系統中平臺的可伸縮性。本文將描述模塊的可伸縮性。
模塊的可伸縮性
WEB交互式系統對模塊的可伸縮性同樣表現為:
- 可擴展性:對于系統新增的功能需求能夠快速響應支持
- 可縮減性:對于系統退化的模塊能夠以最小的修改方式剔除
這里我們提供一套模塊調度的系統架構模式,用于支持單頁富應用系統的設計架構、模塊拆分、模塊重組、調度管理等功能。
模塊
我們定義的模塊是指:從系統中拆分出來的、可與用戶進行交互完成一部分完整功能的獨立單元。
模塊組成
因為這里描述的模塊可獨立與用戶完成交互功能,因此模塊會包含以下元素:
- 樣式:定義模塊的效果
- 結構:定義模塊的結構
- 邏輯:實現模塊的功能
以上元素對于一個WEB系統開發者來說并不陌生,而我們只需要尋求一種形式將這些內容封裝起來即可。
模塊封裝
從模塊的組成我們可以看到系統中分離出來的模塊可能會長成這個樣子,比如module.html就是我們分離出來的一個模塊。
當然這里也可以用腳本文件封裝,樣式和結構采用注入形式。下面以html文件封裝舉例:
<!-- 模塊樣式 --> <style> .m-mdl-1 .a{color:#aaa;} .m-mdl-1 .b{color:#bbb;} /* 此處省略若干內容 */ </style> <!-- 模塊結構 --> <div class="m-mdl-1"> <p class="a">aaaaaaaaaaaaaaaaaaa</p> <p class="b">bbbbbbbbbbbbbbbbbbb</p> <!-- 此處省略若干內容 --> </div> <!-- 模塊邏輯 --> <script> (function(){ var a = 'aaa'; var b = 'bbb'; // 此處省略若干內容 })(); </script>
這個模塊在用戶需要時加載到客戶端,并展現出來跟用戶進行交互,完成功能。但是我們會發現,如果系統預加載了此模塊或者模塊在parse時,這些內容會被直接執行,而這個結果并不是我們需要的,因此我們需要將模塊的各元素文本化處理。文本化處理有多種方式,如作為文本script、textarea等標簽內容,因此module.html里的模塊我們可以封裝成如下樣子,以textarea舉例:
<!-- 模塊樣式 --> <textarea name="css"> .m-mdl-1 .a{color:#aaa;} .m-mdl-1 .b{color:#bbb;} /* 此處省略若干內容 */ </textarea> <!-- 模塊結構 --> <textarea name="html"> <div class="m-mdl-1"> <p class="a">aaaaaaaaaaaaaaaaaaa</p> <p class="b">bbbbbbbbbbbbbbbbbbb</p> <!-- 此處省略若干內容 --> </div> </textarea> <!-- 模塊邏輯 --> <textarea name="js"> (function(){ var a = 'aaa'; var b = 'bbb'; // 此處省略若干內容 })(); </textarea>
管理依賴
從系統中拆分出來的模塊之間是存在有一定關系的,如一個模塊的呈現必須依賴另外一個模塊的呈現。下面我們會以一個簡單的例子來講解模塊之間的依賴管理,如下圖是我們的一個單頁應用系統:
從上圖不難看出整個系統包含以下幾部分內容:
-
日志管理
- 日志:日志列表,可切換收件箱/草稿箱/回收站/標簽
- 標簽:標簽列表,可轉至日志按標簽查看列表
-
博客設置
-
賬號管理
- 基本資料:用戶基本資料設置表單
- 個人經歷:個人經歷填寫表單
-
權限設置:權限設置表單
而這些模塊之間的層級關系則如下所示:
針對交互式系統的這種層級架構典型的模式可以參閱:
然而在WEB交互式系統的實踐過程中我們發現這種模式會存在一些缺陷:
- 由于每個父模塊自己維護了所有的子模塊,因此父子模塊之間耦合性過強,父模塊必須耦合所有子模塊
- 由于模塊之間不能直接越級調用,因此子模塊需要其他模塊協助時必須層層向上傳遞事件,如果層級過深則會影響到系統效率
- 模塊的增刪等變化導致的變更涉及的影響較大,刪除中間節點上的模塊可能導致相鄰的若干模塊的變更
- 多人協作開發系統時存在依賴關系的模塊會導致開發人員之間的緊密耦合
在這里,我們給出了一種基于模塊標識的依賴管理配置方案,可以徹底的將模塊進行解耦,每個模塊可以獨立完整的完成自己的交互功能,而系統的整合則可以通過配置的方式靈活的重組各模塊,模塊的增刪操作只需修改配置即可完成,而無需影響到具體業務邏輯。
下文我們會通過以上例子來講解此方案的原理和實際操作方式。
模塊標識
因為本方案會基于模塊標識做配置,因此在介紹方案之前我們先介紹一下模塊標識,這里我們給模塊標識取名為UMI(Uniform Module Identifier)統一模塊標識,下文簡稱UMI,遵循以下規則約定:
- 格式同URI的Path部分,如 /m/m0/
- 必須以“/”符開始
- 私有模塊必須以“/?”開始
- 承載模塊的依賴關系,如 /m/m0/ 和 /m/m1/ 表明這兩個標識對應模塊的父模塊標識均為 /m
每個UMI均可唯一標識一個模塊及模塊在系統中的依賴關系,在模塊章節我們介紹了一個模塊可以用一個html進行封裝,因此我們可以得到以下結果:
每個UMI均可映射到一個模塊實現文件,這樣我們就可以將模塊從具體實現中解耦出來,對模塊的增刪修改操作只需調整UMI和模塊文件的映射關系即可,而無需涉及具體業務邏輯的修改。
模塊依賴
在解決了模塊與實現分離的問題后,我們接下來需要將層級式的模塊扁平化來解耦模塊之間的依賴關系。回到前面的例子,模塊之間的層級關系如下圖所示:
如果我們將圖中的依賴關系進行抽象分離后,可以發現所有的模塊即可呈現扁平的狀態:
而對于模塊之前的依賴關系的管理,在所有系統中都是一致的,但是每個模塊的具體功能實現是由系統來決定的,不同的系統是截然不同的,因此本方案提供的解決方案主要是用來維護模塊之間的依賴關系的。
從上圖我們可以比較清楚的看到模塊之間的依賴關系呈現樹狀結構,因此我們會以樹的結構來組織維護模塊之間的依賴關系,我們稱之為依賴關系樹。而當我們將這棵樹上的任意節點與根節點之間的路徑用“/”分隔序列化后,發現剛好與我們提供的UMI是匹配的,因此組成系統的模塊的UMI可以跟依賴關系樹的節點一一對應起來,如下圖所示:
在模塊標識章節我們介紹了UMI與模塊封裝文件可以相互映射,因此依賴關系樹上的節點可以直接與模塊的實現文件做一一對應,如下圖所示:
至此,我們將垂直層級依賴的模塊通過依賴關系樹分解成了無任何關系的扁平模塊結構。
模塊組合
模塊只需要有個呈現容器即可渲染出來,因此模塊如果需要能夠做任意組合,只需將模塊分成兩種類型:提供容器的模塊,和使用容器的模塊即可。當然,一個模塊可同時兼具提供容器和使用容器的功能,提供容器的模塊和使用容器的模塊可任意組合。
對于模塊組合的配置代碼范例:
'/m/blog/list/':{ module:'module/layout/blog.list/index.html', composite:{ box:'/?/blog/box/', tag:'/?/blog/tag/', list:'/?/blog/list/', clazz:'/?/blog/class/' } }
調度策略
在將模塊扁平化后,各模塊就可以安排給不同的開發人員進行功能實現和測試了,各模塊完成后根據依賴關系樹進行系統整合,系統整合后各模塊會遵循一定的調度策略進行調度。
模塊狀態
根據模塊調度的階段劃分,模塊的狀態可以分為以下四種:
- 模塊構建:構建模塊結構
- 模塊顯示:將模塊渲染到指定的容器中
- 模塊刷新:根據外界輸入的參數信息獲取數據并展示(這里主要做數據處理)
- 模塊隱藏:模塊放至內存中,回收由顯示和刷新階段產生的額外數據及結構
調度策略主要控制模塊在這幾個階段之間的轉換規則。
模塊顯示
當用戶請求顯示一個模塊時各模塊會遵循以下步驟進行調度,假設請求顯示 /m/blog/list/ 模塊:
- 檢查目標節點到根節點路徑上注冊的模塊,如果注冊的是模塊的實現文件地址,則請求載入模塊實現文件
- 如果節點所在的模塊的所有祖先節點已顯示,則當前模塊可被顯示出來,否則等待祖先模塊的顯示調度
- 模塊載入后根據第二步驟原則嘗試調度目標模塊的顯示
模塊切換
當用戶從一個模塊切換到另外一個模塊時各模塊遵循以下步驟調度,假設從 /m/blog/list/ 切換到 /m/setting/account/edu/ 模塊:
-
查找源模塊與目標模塊的公共父節點
-
從源節點到公共節點之間的模塊調度隱藏操作
-
從根節點到公共節點之間的模塊調度刷新操作
-
從公共節點到目標節點之間的模塊調度顯示操作
消息通道
大部分時候我們不建議使用模塊之前的消息通信,實踐中也存在一些特殊情況會需要模塊之前的消息通信,這里提供兩種方式的消息通訊:
- 點對點的消息:一個模塊發送消息時明確指定目標模塊的UMI
- 觀察訂閱消息:一個模塊可以對外申明發布了什么樣的消息,有需要的模塊可以訂閱該模塊UMI上的消息
上面介紹了模塊可伸縮性的一些原理。在本系列的最后一篇文章中,我們將以網易的NEJ框架為例,對上述原則進行說明。敬請期待!
本作品采用知識共享署名 4.0 國際許可協議進行許可。