前端架構-分層而治,鐵打的MV流水的C

大家好,我是Eluxjs的作者,Eluxjs是一套基於「微模組」和「模型驅動」的跨平台、跨框架『同構方案』,歡迎了解…

文前聲明,以下推斷和結論純屬個人探索,鑒於本人知識水平所限,謬誤在所難免,懇請各位大佬不吝賜教…

為什麼在web前端很少有人會提到分層架構,例如經典MVC架構,這是因為瀏覽器誕生之初就只是作為一個後端數據的GUI渲染器。也就是說整體來看,web1.0時代的整個web前端工程就是一個View層,而ModelController就是指後端,所以根本無需在web前端工程中去提什麼MVC。

然而web生態發展到今天,瀏覽器越來越強大,賦能越來越多,甚至不亞於一個小型作業系統,這時候的Web前端早已不是當初簡單的數據渲染器,隨著PWA、小程式、快應用的推廣,WebAPP已經和傳統的「富客戶端」沒什麼兩樣了。那麼在這種趨勢下,如果我們的Web前端架構還停留在View的時代,那麼顯然是落伍的。

View是最直觀的、最原始的驅動源,解決了所有View的問題也就實現了WebApp的所有功能,前端開發人員的全部工作就是一個又一個的編寫Component。正是因為這種以View為核心的架構思維,導致我們只為解決表層問題而架構,不去思考頂層設計,這也是很多Web前端工程經過幾輪迭代和人員轉手後,很難繼續維護下去的根本原因。

近年來湧現出一些優秀的UI渲染框架,比如React/Vue/Anglar,它們很強大,可以干很多分內和分外的事情。正因為如此,更造成了開發者的惰性,將所有邏輯都直接以最原始的方式寫在View中,不管是Mode還是Controller,不管是業務邏輯、還是渲染邏輯、還是存儲邏輯、還是通訊邏輯、還是路由邏輯…所有各類邏輯疊加各種UI生命周期,全部都耦合在一個Component中。

從視圖驅動到領域驅動

是時候改變我們的架構思維,從視圖驅動升華到領域驅動。雖然視圖驅動是最直觀也是最簡單的一種架構模式,但是我們不僅要解決問題,還要思考如何優雅的解決問題,這也好比是排版設計的區別吧!

或者說,在項目野蠻生長的混沌初期你可以從下往上構建,所建即所得。但到了某個重構的時間點,你還是得站在頂部進行上層設計。

當然,這裡有個默認前提,那就是需要長期維護的中大型項目,小而快的項目無需談各種架構模式…

將UI框架拉下神壇

UI不是工程的全部,UI的指責只有2個:輸入與輸出,僅此而已。所以即便React/Vue再強大,你也應當僅把它們當成一個GUI工具,類似於Java Swing.Net WPF,而非整個工程React/Vue一把梭。

~~ 敲黑板:UI只是指令的收集者、傳達者、回饋者,而不應當成為指令的執行者。 ~~

應用的核心是業務邏輯

現在問你一個問題:

如果沒有UI,你的應用可以通過命令來驅動嗎?

你可能會懟我說,用戶怎麼可能會通過命令來使用我的應用,沒有這樣的業務場景,你這個假設是毫無意義的。然而,這種場景或許並不是為用戶而設,而是為合作夥伴而設、為測試工具而設、為日誌分析而設、為今後的擴展和生態建設而設…

再問你一個問題:

  • UI說:應用要改版,皮膚、交互、頁面組織都要調整,要多久?
  • 產品說:把H5改改,做成小程式、APP吧,要多久?
  • 經理說:React人太難招了,要不我們換成Vue吧,要多久?
  • Leader說:Vue3出來了,我們升級為最新版吧,要多久?
  • 架構師說:Svelte快到飛,我們重構為Svelte吧,要多久?

業務邏輯不變,僅調整UI和其運行平台,問你要多久?這個時候,如果你的全部邏輯都是耦合在某種特定的UI框架中的,那你只能是哀默之心大於死…

UI邏輯與業務邏輯分離

從早期的前後端一體化,到後面的前後端邏輯分離,在到這裡我重提UI邏輯與業務邏輯分離,都只是順著2件事件在做:讓需要內聚的更內聚,讓可以解耦的更鬆散。

  • 業務邏輯抽象,UI邏輯具體:業務邏輯是抽象的數據模型,而UI邏輯則是抽象事物逐層泛化後的具體表現,它們兩個本身就不再同一個Level上。

  • 業務邏輯通用,UI邏輯特殊:UI邏輯通常需要藉助於某個具體的UI框架而表達,而UI框架通常都會與運行平台息息相關,同時也會加入自己的各種各種限制、約束、約定、魔術方法等。所以形象來說,前者是JS國度的普通話,而後者則是方言。

  • 業務邏輯直觀,UI邏輯隱晦:UI渲染本身就是一件很複雜的事情,其間涉及到各種組件的各種生命周期、創建、銷毀、更新、重繪等等、在加上各種優化手段造成的心智負擔。所以你會發現某些業務邏輯放在Redux或是Vuex等純狀態管理框架中,比使用組件內部的Hooks去維護更簡單和直觀。儘管Hooks也可以模擬這些Flux框架,但是不及前者有條理,比如測試、監控、分析、回滾一個Action多容易,你測試、監控、分析、回滾一個Hooks試試…

  • 業務邏輯穩定、UI邏輯多變:UI/UE太感性、太靈活了,帶有很強的個人主觀色彩,經常會被優化、修改、甚至推翻,不像業務邏輯那麼穩定。這也是要將UI邏輯和業務邏輯分開的重要原因,如果你將業務邏輯和UI邏輯寫在一起,你本來穩定的程式碼將受到不穩定程式碼的嚴重騷撓。

鐵打的MV流水的C

現在提MVC是不是過時了?非也,MVC是其實一種最簡單的哲學思想,Model代表抽象事物,View代表具體事物,而Controller則是代表如何在抽象事物和具體事物之間泛化。所以MVC是一種思想,而不是特指某種框架,過時的只會是框架,而非思想。

近年來各種演化如:MVPMVVMMVI等等,其實都是在圍繞C做文章,最終的目的無非就是更好的隔離MV,也就是千方百計分離UI邏輯業務邏輯

Flux架構其實也是一種MVC,而Redux/Vuex/Pinia、以及本人的框架Elux,都可以看作它的變種。下面我們就看看如何利用Flux架構來分離UI邏輯與業務邏輯:

從State到Model

MVVM應用中充斥著狀態,有的用來描述Component的內部狀況,它與Component唇齒相依,跟隨Component誕生和銷毀,這就是我們常說的ComponentState
還有一些狀態,用來描述業務狀態,與具體哪個Component沒有直接關係,不存在復用與銷毀,我們可以稱它為Model

  • ComponentState 是UI邏輯,應當封裝在Component裡面,外界也無需知道。
  • Model 是業務邏輯,反映整個APP的狀況,與Component無關,應當由Flux框架來統一管理。

從Event到Action

用戶通過UI介面產生的人機交互事件,我們習慣叫”Event事件”,而”Event事件”背後的業務目的我們可以叫”Action動作”,它們一個是因一個是果,一個是表一個本。

  • 處理 Event 的 Handler 是UI邏輯,應當寫在UI組件中
  • 處理 Action 的 Handler 是業務邏輯,應當寫在Controller裡面

舉個具體的例子吧:”SubmitLoginForm|提交登錄表單”,通常要完成如下邏輯:

  1. 驗證輸入是否有效
  2. 驗證當前用戶是否已經登錄
  3. 請求後端API,並等待返回
  4. 如果成功,保存用戶資訊,並跳回原頁面
  5. 如果失敗,提示錯誤,並留在原地

這是一個業務動作,因為它可以不依賴哪個具體UI而運行,用戶可能通過「onClick事件」點擊登錄按鈕來觸發,也可能通過「onKeyPress」按下回車鍵來觸發,甚至你可以直接讓用戶通過「Login命令」來觸發。

所以「onSubmitLoginForm()」應當寫在Controller而非UI組件中。
UI組件中只有”onLoginButtonClick()”或”onEnterKeyPress()”,而它們往往也就一句話,就是Dispatch一個Action來觸發Controller中的「onSubmitLoginForm()」

將業務邏輯移出UI組件,這樣UI層就變薄了,回歸到了它的本質:只負責收集業務動作,不負責處理它

model-reusable.jpg

改良Flux框架

傳統的Flux框架也有痛點:

  • 全局中心化管理導致邏輯過於集中;
  • 單實例、不銷毀容易造成資訊累積爆炸;
  • DispatchAction機制過於簡單,不適合處理前因後果的長流程業務。

Elux正是針對以上痛點進行了改良:

  • 雖然堅持全局中心化管理,但Elux提出「微模組」的概念,將應用拆分成獨立自治的一個個「微模組」,每個微模組僅處理自己領域內的事情。
  • 不再單實例,每次路由變化都會產生一個新的空白Store,然後重新挑選有用的狀態掛載,類似一種垃圾回收機制。
  • 提出了ActionBus的概念,讓Action作為Model中的事件來廣播。
  • 讓Action的處理鏈條具備「協程」機制,更好的協同各業務動作之間的關聯。

Elux踐行的分層而治

正是因為遵循了輕UI、重Model的設計思想,讓Elux可以掛接React/Vue等各種不同的UI框架,它們已經變得沒那麼重要了。

正式因為分離了UI邏輯和業務邏輯,讓Elux可以用一種工程模式開發Web(瀏覽器)、Micro(微前端)、SSR(伺服器渲染)、MP(小程式)、APP(手機應用)。

此致!歡迎交流://eluxjs.com

three-layers.jpg