前端代碼的整潔之道 | 技術頭條
打開今日頭條,查看更多圖片在前端開發過程中,你有沒有遇到過由於代碼交互太多太重時,想改動一行代碼「牽一髮而動全身」;使用框架很爽,可框架綁定應用卻很麻煩?那麼如何解決呢?
你需要「前端整潔」。
作者 | Phodal
責編 | 伍杏玲
前端的惡夢
在我最近的一個項目里,我使用了 Angular 和混合應用技術編寫了一個實時聊天應用。為了方便這個應用直接修改,無縫地嵌入到其它應用程序中。我盡量減少了 Component 和 Service 的數量。
然而,由於交互複雜 Component 的數量也不能減少。隨後,當我們完成了這個項目的時候,主要的組件代碼差不多有 1000 行。這差不多是一個複雜的應用的代碼數。在我試圖多次去重構代碼時,我發現這並不是一件容易的事:太多的交互。
導致了 UI 層的代碼,很難被抽取出去。我還能做的事情是將一些業務邏輯抽取出來,只是怎麼去抽取了?這成了我的一個疑惑。
MVP 嘛,邏輯不都是放到 Presenter 里,還有其它的招嗎?
AVR is Evil
Angular、Vue 和 React 都是一些不錯的框架,但是它們都是惡魔,因為我們綁定了框架。我們可以很快地從一個 React 的框架,遷移應用到其它類 React 框架,諸如 Preact;我們可以從一個類似於 Vue 的框架,遷移應用到其它類 Vue 的應用。但是我們很難從 React 遷移到 Angular,又或者是 Vue 遷移到 Angular。
萬一有一天某個框架的核心維護人員健康狀況不好,那麼我們可能就得重寫整個應用。這對於一個技術人員/Tech Lead/項目經驗/業務人員來說,這種情況是不可接受的。
所以,為了應對這些框架帶來的問題,我們選擇 Web Components 技術,又或者是微前端技術,從架構上切開我們的業務。但是它們並不是銀彈,它們反而是一個累贅,限定了高版本的瀏覽器,制定了更多的規範。
與此同時,不論是微前端技術還是 Web Components,它們都沒有解決一個問題:框架綁定應用。
框架綁定應用,就是一種災害。沒有人希望哪一天因為 Java 需要高額的付費,而導致我們選擇重寫整個應用。
組件化及 Presenter 過重
應對頁面邏輯過於重的問題,我們選擇了組件化。將一個頁面拆分成一系列的業務組件,再進一步地對這些業務組件進行地次細分,形成更小的業務組件,最後它們都依賴於組件庫。
可是呢,細化存在一個問題是:更難以擺脫的框架綁定。與此同時,我們大量的業務邏輯仍然放置在 Presenter 里。我們的 Presenter 充滿了大量的業務邏輯和非業務邏輯:
- 頁面展示相應的邏輯。諸如點擊事件、提交表單等等。
- 狀態管理。諸如是否展示,用戶登錄狀態等等。
- 業務邏輯。諸如某個字元串,要用怎樣的形式展示。
- 數據持續化。哪些數據需要存儲在 LocalStorage,哪些數據存儲在 IndexedDB 里?
為了應對 Presenter 過重的問題,我們使用了 Service 來處理某一塊具體的業務,我們使用了 Utils、Helper 來處理一些公共的邏輯。哪怕是如此,我們使用 A 框架編寫的業務邏輯,到了 B 框架中無法復用。
直到我最近重新接觸了 Clean Architectrue,我發現 Presenter 還是可以進一步拆分的。
整潔的前端架構
Clean Architecture 是由 Robert C. Martin 在 2012 年提出的。最早,我只看到在 Android 應用上的使用,一來 Android 開發使用的是 Java,二來 Android 應用有很重的 View 層。
與此同時,在 7 年的時間裡,由於前後端的分離,UI 層已經從後端的部分消失了。當然了,你也可以說 JSON 也是一種 View(至少它是可見的)。儘管,還存在一定數量的後端渲染 Web 應用,但是新的應用幾乎不採用這樣的模式。
但是,在 9012 年的今天,前端應用走向了 MV* 的架構方案,也有了一層很重的 View 層。類似於過去的後端應用,或者後端應用。相似的架構,也可以在前端項目中使用。
整潔架構
Robert C. Martin 總結了六邊形架構(即埠與適配器架構):DCI (Data-Context-Interactions,數據-場景-交互)架構、BCI(Boundary Control Entity,Boundary Control Entity)架構等多種架構,歸納出了這些架構的基本特點:
- 框架無關性。系統不依賴於框架中的某個函數,框架只是一個工具,系統不能適應於框架。
- 可被測試。業務邏輯脫離於 UI、資料庫等外部元素進行測試。
- UI 無關性。不需要修改系統的其它部分,就可以變更 UI,諸如由 Web 界面替換成 CLI。
- 資料庫無關性。業務邏輯與資料庫之間需要進行解耦,我們可以隨意切換 LocalStroage、IndexedDB、Web SQL。
- 外部機構(agency)無關性。系統的業務邏輯,不需要知道其它外部介面,諸如安全、調度、代理等。
如你所見,作為一個普通(不分前後端)的開發人員,我們關注於業務邏輯的抽離,讓業務邏輯獨立於框架。
而在前端的實化,則是讓前端的業務邏輯,可以獨立於框架,只讓 UI(即表現層)與框架綁定。一旦,我們更換框架的時候,只需要替換這部分的業務邏輯即可。
為此,基於這個概念 Robert C. Martin 繪製出了整潔架構的架構圖:
Clean Architecture
如圖所示,Clean Architecture 一共分為四個環,四個層級。環與環之間,存在一個依賴關係原則:源代碼中的依賴關係,必須只指向同心圓的內層,即由低層機制指向高級策略。其類似於 SOLID 中的依賴倒置原則:
- 高層模塊不應該依賴低層模塊,兩者都應該依賴其抽象。
- 抽象不應該依賴細節,細節應該依賴抽象。
與此同時,四個環都存在各自核心的概念:
- 實體(Entities),又稱領域對象或業務對象,實體用於封裝企業範圍的業務規則。
- 用例(Use Cases),交互器,用例是特定於應用的業務邏輯。
- 介面適配器(Interface Adapters),介面適配器層的主要作用是轉換數據。
- 框架和驅動(Frameworks and Drivers),最外層由各種框架和工具組成,比如 Web 框架、資料庫訪問工具等。
這個介紹可能有些簡單,讓我更詳細的解釋:
實體(Entities),實體用於封裝企業範圍的業務規則。實體可以是擁有方法的對象,也可以是數據結構和函數的集合。如果沒有企業,只是單個應用,那麼實體就是應用里的業務對象。這些對象封裝了最通用和高層的業務規則,極少會受到外部變化的影響。任何操作層面的改動都不會影響到這一層。
用例(Use Cases),用例是特定於應用的業務邏輯,一般用來完成用戶的某個操作。用例協調數據流向或者流出實體層,並且在此過程中通過執行實體的業務規則來達成用例的目標。用例層的改動不會影響到內部的實體層,同時也不會受外層的改動影響,比如資料庫、UI 和框架的變動。只有而且應當應用的操作發生變化的時候,用例層的代碼才隨之修改。
介面適配器(Interface Adapters)。介面適配器層的主要作用是轉換數據,數據從最適合內部用例層和實體層的結構轉換成適合外層(比如數據持久化框架)的結構。反之,來自於外部服務的數據也會在這層轉換為內層需要的結構。
框架和驅動(Frameworks and Drivers)。最外層由各種框架和工具組成,比如 Web 框架、資料庫訪問工具等。通常在這層不需要寫太多代碼,大多是一些用來跟內層通信的膠水代碼。這一層包含了所有實現細節,把實現細節鎖定在這一層能夠減少它們的改動對整個系統造成的傷害。
概念就這麼扯到這裡吧,然後看看相應的實現。
Clean Architecture 數據流
上圖中的右側部分表示的是相應的數據流,數據從 Controller 流出,經過 Use Case(用例)的輸入埠,然後通過 Use Case 本身,最後通過 Use Case 輸出埠返回給 Presenter。
讓我們來看一個較為直觀的例子:
Clean Architecture 數據流
上圖(來源,見參考文章)是一個 Android 應用的數據流示意圖。
對於只懂得前端的開發大致說明一下,Android 的 View 和 Presenter 的關係。在前端應用中,我們假設以使用 Component 來表示一個組件,如 Angular 中的 HomepageComponent。而這個 HomepageComponent 中,它必然充滿了一些無關於頁面顯示的邏輯,比如從後端獲取顯示數據之類的。
而 Java 的寫法本身是比較臃腫的,所以在 Android 的 Activity 中就會充斥大量的代碼。為此,Android 的開發人員們,採用了 MVP 架構,通過 Presenter 來將與顯示無關的行為,從 View 中抽離出來。
優缺點
優點:
- 框架無關性
- 可被測試
- UI 無關性
- 資料庫無關性
- 外部機構無關性
- 定義了特定功能的代碼放在何處
- 可以在多個項目共享業務邏輯
相應的它還有大量的缺點:
- 過於複雜。
- 數據需要經過多層處理,Repository 轉為 Entity,經過 Usecase 轉為 Model,再交由 Presenter 處理,最後交由 View 顯示。一個示例如下所示(源自Android-Clean-Boilerplate):
- MainActivity->MainPresenter->WelcomingInteractor-> WelcomeMessageRepository->WelcomingInteractor->MainPresenter->MainActivity
- 過度設計。
- 事到如今,我們做了大量的設計,對於一個簡單的工程來說,這樣的模式可能是過度式的設計。
- 大量的模板式代碼。
- Usecase、Model 等一系列重複的模板式代碼。
- 陡峭的學習曲線。
- 不用我多說,看這篇文章的長度。
所以,在採用之前,請再次考慮一下,你的應用是否足夠的複雜:業務上的複雜度,代碼上的複雜度等等。
前端 Clean 架構
說了,這麼多,讓我們來結合一下前端,設計一下新的前端架構。
客戶端 Clean 架構 + MVP
與後端架構相比, Android 的 MVP 架構 + Clean 架構更與前端相似,為此我們再說看看它們結合的一個示例:
Android Clean Architecture
與上一個數據流的相比,這個數據流圖更容易落地。其與傳統的 MVP(Model-View-Presenter)架構相比:
MVP
基於 Clean Architecture 方案時,則多了一個領域層(圖中的 Domain Layer,即業務層),在這一層領域層里,放置的是系統相關的用例(Usecase),而用例所包含的則是相應的業務邏輯。
Clean Architecture + MVP + 組件化
上述的 MVP + Clean Architecture 的架構方式,對於前端應用的架構設計來說,也是相當合適的。稍有不同的是,我們是否有必要將一個組件分為 Presenter + View。以我的角度來說,對於大部分前端應用來說,並沒有這麼複雜的情況,因為前端有組件化架構。
所以,最後對於我們的前端應用而言,架構如下圖所示:
Clean MVP 組件化
這裡,只是對於 Presenter 進行更細一步的細化,以真實的模式取代了 MVP 中的 Presenter。
實踐
值得注意的是,我們在這裡違反了依賴倒置原則。原因是,這裡的注入帶來了一定的前端複雜度,而這個注入並非是必須的——對於大部分的前端應用而言,只會有單一的數據源,那便是後端數據。
單體式分層架構
在我起初設計的版本里,參照的 Clean Angular 工程(Angular Clean Architecture)里,其採用的是單體式的 Clean Architecture 分層結構:
├── core
│ ├── base // 基礎函數,如 mapper 等
│ ├── domain // 業務實體
│ ├── repositories // repositories 模型
│ └── usecases // 業務邏輯
├── data // 數據層
│ └── repository // 數據源實現
└── presentation // 表現層
這個實現還是相當不錯的,就是過於重視理論——抽象相當的繁瑣,導致有點不接地氣。我的意思說,沒有多少前端人員,願意按照這個模式來寫。
微服務式分層架構
考慮到 usecase 的業務相關性,及會存在大師的 usecase,我便將 usecase 移到了 data 目錄,也存在一定的不合理性。後來,我的同事澤杭,一個有豐富的 React 經驗前端開發,他提出了 Redux 中的相關結構。最後,我們探討出了最後的目錄結構:
├── core // 核心代碼,包含基本服務和基礎代碼
├── domain // 業務層代碼,包含每個業務的單獨 Clean 架構內容
│ └── elephant // 某一具體的業務
├── features // 公共頁面組件
├── protected // 有許可權的頁面
├── public // 公共頁面
└── shared // 共享目錄
對應的 elephant 是某一個具體的業務,在該目錄下包含了一個完整的 Clean Architecture,相應的目錄和文件如下所示:
├── model
│ └── elephant.model.ts // 核心業務模型
├── repository
│ ├── elephant-web-entity.ts // 數據實體,簡單的數據模型,用來表示核心的業務邏輯
│ ├── elephant-web-repository-mapper.ts // 映射層,用於核心實體層映射,或映射到核心實體層。
│ └── elephant-web.repository.ts // Repository,用於讀取和存儲數據。
└── usecases
└── get-elephant-by-id-usecase.usecase.ts // 用例,構建在核心實體之
之上,並實現應用程序的整個業務邏輯。
我一直思考這樣的模式是否有問題,直到我看到我司大佬 Martin Folwer 寫下的一篇文章《PresentationDomainDataLayering》,終於有人背鍋了。文章中提到了這圖:
分層
這個分層類似於微服務的概念,在我所熟悉的 Django 框架中也是這樣的結構。也因此從理論和實踐上不看,並不存在任何的問題。
它不是一顆銀彈。使用 MVP 並不妨礙開發人員將 UI 邏輯放在 View 中,使用 Clean Architecture 不會阻止業務邏輯泄漏到表示層。
作者簡介:黃峰達(Phodal),ThoughtWorks Senior Consultant,CSDN 博客專家。長期活躍於 GitHub、CSDN,專註於物聯網和前端領域。出版著作《自己動手設計物聯網》,以及《Growth:全棧增長工程師指南》等六本電子書,並譯有《物聯網實戰指南》。
本文經授權轉自作者公眾號「Phodal」。
相關資料:《整潔架構之道》
源碼:https://github.com/phodal/clean-angular
TAG:CSDN |