移動端APP元件化架構實踐

架構師修行手冊發表於2023-11-06


來源:京東技術

導讀

本文透過以下問題來介紹元件化這種開發架構的思想和常見的一些問題:

為什麼需要元件化

元件化過程中會遇到的挑戰和選擇

如何維護一個高質量的元件化專案




01 
前言


在今年的敏捷團隊建設中,我透過Suite執行器實現了一鍵自動化單元測試。Juint除了Suite執行器還有哪些執行器呢?由此我的Runner探索之旅開始了!

對於中大型移動端APP開發來講,元件化是一種常用的專案架構方式。最近幾年在工作專案中也一直使用元件化的方式來開發,在這過程中也積累了一些經驗和思考。主要是來自在日常開發中使用元件化開發遇到的問題以及和其他開發同學的交流探討。

本文透過以下問題來介紹元件化這種開發架構的思想和常見的一些問題:

  • 為什麼需要元件化

  • 元件化過程中會遇到的挑戰和選擇

  • 如何維護一個高質量的元件化專案

提示:本文說的元件化工程Multirepo使用獨立的git倉庫來管理元件。



02 
  

元件化可以帶來什麼

  


理解,首先 MCube 會依據模板快取狀態判斷是否需要網路獲取最新模板,當獲取到模板後進行模板載入,載入階段會將產物轉換為檢視樹的結構,轉換完成後將透過表示式引擎解析表示式並取得正確的值,透過事件解析引擎解析使用者自定義事件並完成事件的繫結,完成解析賦值以及事件繫結後進行檢視的渲染,最終將目標頁面展示到螢幕。

2.1  單一工程架構遇到的問題


    
在元件化架構之前,傳統使用的工程架構主要是以Monolithic方式的單一工程架構,也就是將所有程式碼放在單個程式碼倉庫裡管理。單一工程架構使用了這麼多年為什麼突然遇到了問題,這也引入了APP專案開發的一個大背景,現有中大型APP專案變的越來越複雜:

  • 多APP專案並存 - 集團內部存在多個APP專案,不同APP希望可以複用現有元件能力快速搭建出新的APP。

  • 功能增多 - 隨著專案功能越來越多,程式碼量增多。同時需要更多的開發人員參與到專案中,這會增加開發團隊之間協作的成本。

  • 多語言/多技術棧 - 引入了更多的新技術,例如使用一種以上的跨平臺UI技術用於快速交付業務,不同的程式語言、音影片、跨平臺框架,增加了整個工程的複雜度。

以上這些業務發展的訴求就給傳統單一工程架構方式帶來了很多新的技術要求:

工程效率

  • 工程程式碼量過大會導致編譯速度緩慢。

  • 單git工程提交同時可能帶來更多的git提交衝突和編譯錯誤。

質量問題

  • 如何將git提交關聯到對應的功能模組需求。發版時進行合規檢查避免帶入不規範的程式碼,對整個功能模組回滾的訴求。

  • 如何在單倉庫中管控這麼多開發人員的程式碼許可權,儘可能避免不安全的提交併且限制改動範圍。

更大範圍的元件複用

  • 基礎元件從支援單個APP複用到支援多個APP複用。

  • 不只是基礎能力元件,對於業務能力元件也需要支援複用。(例如一個頁面元件同時在多個APP使用)

  • 跨平臺容器需要複用底層元件能力避免重複開發,同時不同跨平臺容器API需要儘量保持統一,底層基礎設施向容器化發展支援業務跨APP複用。

跨技術棧通訊

  • 由於頁面導航多技術棧混合共存,頁面路由需要支援跨技術棧。

  • 跨元件通訊需要支援跨語言/跨技術棧通訊。

更好的解耦

  • 頁面解耦。由於頁面導航棧混合共存,頁面自身不再清晰的知道上游和下游頁面由什麼技術棧搭建,所以頁面路由需要做到完全解耦隔離技術棧的具體實現。

  • 業務元件間維持松耦合關係,可以靈活新增/移除,基於現有元件能力快速搭建出不同的APP。

  • 對於同一個服務或頁面可以外掛化方式靈活提供多種不同的實現,不同的APP宿主也可以提供不同的實現並且提供A/B能力。

  • 由於包體積限制和不同元件包含相同符號導致的符號衝突問題,在複用元件的時候需要儘可能引入最小依賴原則降低接入成本。

2.2  元件化架構的優勢


    
基於以上這些問題,現在的元件化架構希望可以解決這些問題提升整個交付效率和交付質量。

元件化架構通常具備以下優點:

  • 程式碼複用 - 功能封裝成元件更容易複用到不同的專案中,直接複用可以提高開發效率。並且每個元件職責單一使用時會帶入最小的依賴。
  • 降低理解複雜度 - 工程拆分為小元件以後,對於元件使用方我們只需要透過元件對外暴露的公開API去使用元件的功能,不需要理解它內部的具體實現。這樣可以幫助大家更容易理解整個大的專案工程。
  • 更好的解耦 - 在傳統單一工程專案中,雖然可以使用設計模式或者編碼規範來約束模組間的依賴關係,但是由於都存放在單一工程目錄中缺少清晰的模組邊界依然無法避免不健康的依賴關係。元件化以後可以明確定義需要對外暴露的能力,對於模組間的依賴關係可以進行強約束限制依賴,更好的做到解耦。對一個模組的新增和移除都會更容易,並且模組間的依賴關係更加清晰。
  • 隔離技術棧 - 不同的元件可以使用不同的程式語言/技術棧,並且不用擔心會影響到其他元件或主工程。例如在不同的元件內可以自由選擇使用Kotlin或Swift,可以使用不同的跨平臺框架,只需要透過規範的方式暴露出頁面路由或者服務方法即可。
  • 獨立開發/維護/釋出 - 大型專案通常有很多團隊。在傳統單一專案整合打包時可能會遇到程式碼提交/分支合併的衝突問題。元件化以後每個團隊負責自己的元件,元件可以獨立開發/維護/釋出提升開發效率。
  • 提高編譯/構建速度 - 由於元件會提前編譯釋出成二進位制庫進行依賴使用,相比編譯全部原始碼可以節省大量的編譯耗時。同時在日常元件開發時只需要編譯少量依賴元件,相比單一工程可以減少大量的編譯耗時和編譯錯誤。
  • 管控程式碼許可權 - 透過元件化將程式碼拆分到不同元件git倉庫中,可以更好的管控程式碼許可權和限制程式碼變更範圍。
  • 管理版本變更 - 通常會使用CocoaPods/Gradle這類依賴管理工具來管理專案中所有的元件依賴。因為每一個元件都有一個明確的版本,這樣可以透過對比APP不同版本打包時的元件依賴表很清晰的識別元件版本特性的變更,避免帶入不合規的元件版本特性。並且在出現問題時也很方便透過配置表進行回滾撤回。

提示:元件化架構是為了解決單一工程架構開發中的問題。如果你的專案中也會遇到這些痛點,那可能就需要做元件化。



03 
  

元件化遇到的挑戰

  


理解,首先 MCube 會依據模板快取狀態判斷是否需要網路獲取最新模板,當獲取到模板後進行模板載入,載入階段會將產物轉換為檢視樹的結構,轉換完成後將透過表示式引擎解析表示式並取得正確的值,透過事件解析引擎解析使用者自定義事件並完成事件的繫結,完成解析賦值以及事件繫結後進行檢視的渲染,最終將目標頁面展示到螢幕。

雖然元件化架構可以帶來這麼多收益,但不是隻要使用元件化架構就可以解決所有問題。通常來講當使用一種新的技術方案解決現有問題的時候也會帶來一些新的問題,元件化架構能帶來多少收益主要取決於整個工程元件化的質量。那在元件化架構中如何去評估專案工程的元件化架構質量,需要關注哪些問題。對於軟體架構來講,最重要的就是管理元件實體以及元件間的關係。所以對於元件化架構來講主要是關注以下三個問題:

  • 如何劃分元件的粒度、元件職責邊界在哪裡?

  • 元件間的依賴關係應該如何管理?

  • 元件間應該使用哪種方式呼叫和通訊?

1. 元件拆分的粒度、元件職責邊界在哪裡?

某種程度上元件拆分粒度也是一種平衡的藝術,需要在效率和質量之間找到一種相對的平衡。元件拆分粒度太粗:導致元件間耦合緊密,並不能利用更好的複用/解耦/提高編譯速度這些優勢。元件拆分粒度太細:導致需要維護更多的元件程式碼倉庫、功能變更可能涉及多個元件程式碼的修改/釋出,這些都會帶來額外的成本,同時元件過多也會導致元件依賴查詢過程變的更復雜更慢。
元件的職責也會影響對於元件的拆分方式:每個元件的定位是什麼,應該包含什麼樣的功能,是否可以被複用,新增某個功能的時候應該建立新元件還是新增到現有元件,當元件複雜到一定程度時是否需要拆分出新個元件。
在拆分元件前需要提前去思考這些問題。

2. 元件間的依賴關係應該如何管理?

元件間的依賴方式主要分為直接強耦合依賴和間接松耦合依賴。強耦合依賴是對依賴的元件直接使用對應的API進行呼叫,這種呼叫方式優點是簡單直接效能更好,缺點是一種完全耦合的呼叫方式。(基礎元件通常使用這種方式)。松耦合依賴主要是透過通知、URL Scheme、ObjC Runtime、服務介面、事件佇列等通訊方式進行間接依賴呼叫。雖然效能相對差一點,但這是一種相對耦合程度比較低並且靈活的依賴方式。(業務元件通常使用這種方式)
元件間的依賴關係很重要是因為在長期的專案開發演化過程中很容易形成一種複雜的網狀依賴關係。雖然看似使用元件化的方式將模組拆分成不同的元件,但是元件間可能存在很多相互交叉的依賴耦合關係,很多元件都被其他元件直接依賴或隱式間接依賴。這樣就背離了元件化架構更好的解耦、更好的複用、更快速的開發/編譯/釋出的初衷。
所以需要制定一套規範去約束和規範元件間的依賴關係:兩個元件之間是否可以依賴,元件間依賴方向,選擇強耦合依賴還是松耦合依賴。

3. 元件間松耦合依賴關係應該使用哪種方式呼叫和通訊?

松耦合依賴通常可以使用通知、URL Scheme、ObjC Runtime、服務介面、事件佇列等方式通訊進行間接呼叫,但是使用哪種方式更好業界也有很多爭論,並且每種方式都有一些優缺點。通常在專案中會根據不同的使用場景至少會選擇2種通訊方式。
耦合程度低的方式例如URL Scheme,可以做到完全解耦相對比較靈活。但是無法利用編譯時檢查、無法傳遞複雜物件、呼叫方/被呼叫方都需要對引數做大量的正確性檢查和對齊。同時可能無法檢測對應的呼叫方法是否存在。
耦合程度高的方式例如服務介面,需要對服務介面方法進行強依賴,但是可以利用編譯時檢查、傳遞複雜物件、並且可以更好的支援Swift特性。
需要在解耦程度、容易使用、安全上找到一種合適的方式。

提示:這裡的耦合程度高是相對於耦合程度低的方式進行比較,相比直接依賴對應元件依然是一種耦合程度低的依賴關係。



04 
  元件化架構實踐規範和原則  


理解,首先 MCube 會依據模板快取狀態判斷是否需要網路獲取最新模板,當獲取到模板後進行模板載入,載入階段會將產物轉換為檢視樹的結構,轉換完成後將透過表示式引擎解析表示式並取得正確的值,透過事件解析引擎解析使用者自定義事件並完成事件的繫結,完成解析賦值以及事件繫結後進行檢視的渲染,最終將目

基於以上這些元件化架構的問題,需要一些元件化架構相關的規範和原則來幫助做好元件化架構,後面主要會圍繞以下三點進行介紹:

  • 元件拆分原則 - 拆分思想和最佳實踐指導元件拆分

  • 元件間依賴 - 最佳化元件間依賴關係跨元件呼叫/通訊方式的選擇

  • 質量保障 - 避免在持續的工程演化過程中工程質量逐漸劣化。主要包含安全卡口和CI檢查

4.1  工程例項


    

接下來以一個典型的電商APP架構案例來介紹一個元件化工程。這個案例架構具備之前所說現有中大型APP架構的一些特點,多元件、多技術棧、業務間需要解耦、複用底層基礎元件。基於這個案例來介紹上面的三點原則。

移動端APP元件化架構實踐圖1.

4.2  元件拆分原則


    

移動端APP元件化架構實踐圖2.

元件拆分最重要是幫助梳理出元件職責以及元件職責的邊界。元件劃分也會使用很多通用的設計原則和架構思想。

4.2.1使用分層思想拆分

通常可以首先使用分層架構的思想將所有元件縱向拆分為多層元件,上面層級的元件只能依賴下面層級的元件。一般至少可以劃分為四層元件:

  • 基礎層 - 提供核心的與上層業務無關的基礎能力。可以被上層元件直接依賴使用。

  • 業務公共層 - 主要包含頁面路由、公共UI元件、跨元件通訊以及服務介面,可被上層元件直接依賴使用。
  • 業務實現層 - 業務核心實現層,包含原生頁面、跨平臺容器、業務服務實現。元件間不能直接依賴,只能透過呼叫頁面路由或跨元件通訊元件進行使用。
  • APP宿主層 - 主要包含APP主工程、啟動流程、頁面路由註冊、服務註冊、SDK引數初始化等元件,用於構建打包生成相應的APP。

劃分層級可以很好地指導進行元件拆分。在拆分元件時需要先識別它應該在哪一層,它應該以哪種呼叫方式被其他元件使用,新新增的功能是否會產生反向依賴,幫助規範元件間的依賴關係。同時按層級拆分元件也有利於底層基礎元件的複用。
以下場景使用分層思想就很容易識別:

基礎元件依賴業務元件

例子:APP內業務發起網路請求通常需要攜帶公共引數/Cookie。

  • 沒有元件分層約束 - 網路庫可能會依賴登入服務獲取使用者資訊、依賴定位服務獲取經緯度,引入大量的依賴變成業務元件。
  • 有元件分層約束 - 網路庫作為一個基礎元件,它不需要關注上層業務需要攜帶哪些公共業務引數,同時登入/定位服務元件在網路庫上層不能被反向依賴。這時候會考慮單獨建立一個公共引數管理類,在APP執行時監聽各種狀態的變更並呼叫網路庫更新公共引數/Cookie。

業務元件間依賴方向是否正確

登入狀態切換經常會涉及到很多業務邏輯的觸發,例如清空本地使用者快取、地址快取、清空購物車資料、UI狀態變更。

  • 沒有元件分層約束 - 可能會在登入服務內當登入狀態切換時呼叫多個業務邏輯的觸發,導致登入服務引入多個業務元件依賴。

  • 有元件分層約束 - 登入元件只需要在登入狀態切換時發出通知,無需知道登入狀態切換會影響哪些業務。業務邏輯應該監聽登入狀態的變更。

識別基礎元件還是業務元件

雖然很多場景下很容易能識別處理出來一個功能應該歸屬於基礎元件還是業務元件,例如一個UI控制元件是基礎元件還是業務元件。但是很多時候邊界又非常的模糊,例如一個新增購物車按鍵應該是一個基礎元件還是業務元件呢。

  • 基礎元件 - 如果不需要依賴業務公共層那應當劃分為一個基礎元件。

  • 業務元件 - 依賴了業務公共層或者網路庫,那就應該劃分為一個業務元件。

分層思想可以很好地幫助管理元件間的依賴關係,並且明確每個元件的職責邊界。

4.2.2基礎/業務元件拆分原則

劃分基礎/業務元件主要是為了強制約束元件間的依賴關係。以上面的元件分層架構為例:

  • 基礎元件 - 基礎元件可被直接依賴使用,使用方呼叫基礎元件對外暴露API直接使用。基礎層、業務公共層都為基礎元件。
  • 業務元件 - 業務元件不可被直接依賴使用,只能透過間接通訊方式進行使用。APP宿主層和業務實現層都為業務元件。

提示:這裡的業務元件並不包含業務UI元件。

4.2.3基礎元件拆分

基礎元件通常根據職責單一原則進行拆分比較容易拆分,但是會有一些拆分場景需要考慮:

使用外掛元件拆分基礎元件擴充套件能力

將核心基礎能力和擴充套件能力拆分到不同的元件。以網路庫為例,除了提供最核心的介面請求能力,同時可能還包含一些擴充套件能力例如HTTPDNS、網路效能檢測、弱網最佳化等能力。但這些擴充套件能力放在網路庫元件內部可能會導致以下問題:

  • 擴充套件能力會使元件自身程式碼變的更加複雜。

  • 使用方不一定會使用所有這些擴充套件能力違反了最小依賴原則。帶來更多的包體積,引入更多的元件依賴,增加模組間的耦合度。
  • 相關的擴充套件能力不支援靈活的替換/插拔。

所以這種場景可以考慮根據實際情況將擴充套件能力拆分到相應的外掛元件,使用方需要時再依賴引入對應外掛元件。

4.2.4業務元件拆分

業務頁面拆分方式

針對業務頁面可以使用技術棧、業務域、頁面粒度三種方式進行更細粒度的劃分,通常至少要拆分到技術棧、業務域這一層級,頁面粒度拆分根據具體頁面複雜度和複用訴求。

  • 基於技術棧進行拆分 - 不同的技術棧需要拆分到不同的元件進行管理。

  • 基於業務域進行拆分 - 將同一個業務域的所有頁面拆分一個元件,避免不同業務域之間形成強耦合依賴關係,同一個業務域通常會有更多複用和通訊的場景也方便開發。例如訂單詳情和訂單列表可放置在一起管理。

  • 基於頁面粒度進行拆分 - 單個頁面複雜度過高或需要被單獨複用時需要拆分到一個單個元件管理。
提示:放置在單一元件內的多個頁面之間也應適當降低耦合程度。

4.2.5第三方庫

第三方庫應拆分單獨元件管理

第三方庫應使用獨立的元件進行管理,一方面有利於元件複用同時避免多個重複第三方庫導致符號衝突,另一方面有利於後續升級維護。

4.2.6一些提示

減少使用通用聚合公共元件

為了避免拆分過多的元件,通常會建立聚合元件將一些程式碼量不多/功能相似的類放到同一個元件內,例如Foundation元件、UI元件。但是很多時候會存在濫用的場景,應當警惕這類公共聚合元件。下面是一些公共聚合元件容易濫用的場景:

  • 新增一個新功能不知道應當加在哪裡時,就加到公共聚合元件內,時間久了以後公共元件依賴特別多;

  • 公共元件新增了一個非常複雜的能力,導致複雜度變高或者引入大量依賴;

  • 太多能力聚合到一起。例如將網路庫、圖片庫這些能力放在同一個元件內;

  • 基礎/業務UI元件沒有拆分。基礎UI元件通常只提供最基礎的UI和非常輕量的邏輯,業務元件通常會充當基礎UI元件的資料來源以及業務邏輯。

但是也不能完全避免使用聚合公共元件,不然會導致產生更多的小元件增加維護成本。但是將一個能力新增到公共聚合元件時可以根據以下幾個條件來權衡:

  • 是否會引入大量新的依賴

  • 功能複雜度、程式碼數量,太複雜的不應該新增到公共元件

  • 能力是否需要被單獨複用,需要單獨複用就不應該新增到公共元件

第三方庫考慮不直接對外暴露使用

當存在以下情況時可考慮對第三方庫進行適當的封裝避免直接暴露第三方庫:

  • 使用方通常只需要使用少量API,第三方庫會對外暴露大量API增加使用難度,同時可能導致一些安全問題

  • 對外隱藏具體實現,方便後續更換其他第三方庫、自實現、第三方庫發生Break Change變更時升級更容易

  • 需要封裝擴充套件一些能力讓使用方使用起來更容易

以網路庫為例:

1.通常需要對接公司內部的API閘道器能力所以需要適當做一些封裝,例如簽名或者加密策略。

2.使用方通常只需要用到一個通用的請求方法無需對外暴露太多API。

3.為了安全通常需要對業務方隱藏一些方法避免錯誤呼叫,例如全域性Cookie修改等能力。

4.對外隱藏具體第三方庫可以方便變更。

第三方庫儘可能避免直接修改原始碼

第三方庫元件儘可能不要直接修改原始碼,除修復Bug/Crash之外儘可能避免帶入其他功能程式碼導致後面更新困難。需要新增功能時可以透過在其他元件內使用第三方庫對外暴露的API進行能力擴充套件。

4.3  元件間依賴關係


    

4.3.1業務元件間通訊方式選擇

松耦合通訊方式對比

移動端APP元件化架構實踐圖3.

基於以上表格中各種方案的優缺點,個人推薦使用URL Scheme協議作為頁面路由通訊方式,使用服務介面提供業務功能服務。通知訂閱場景可使用通知或RxSwift方式提供一對多的訂閱能力。

4.3.2服務介面

服務介面對應的實現和頁面是否需要拆分

以購物車服務為例,購物車介面服務提供了新增購物車的能力。加車服務具體的實現應該放在購物車頁面元件內還是獨立出來放置在單獨的元件。將購物車服務實現和購物車頁面拆分的優點是購物車服務和購物車頁面更好的解耦,都能單獨支援複用。缺點是開發效率降低,修改購物車功能時可能會涉及到同時修改購物車服務元件和購物車頁面元件。
所以在需要單獨複用服務或頁面的場景時可考慮分別拆分出單個元件(例如購物車服務作為一種通用能力提供給上層跨平臺容器能力)。但即使在同一個元件內也建議對服務和頁面使用分層設計的方式進行解耦。

服務介面是否需要拆分

一般專案可能至少會有10+個服務介面,這些服務介面應該統一存放在單個元件還是每個介面對應一個元件。

  • 統一存放:優點是一起管理更快捷方便。缺點是所有介面對應一個元件版本,不能支援單一介面使用不同版本,不利於需要跨APP複用的專案。並且使用方可能會引入大量無用的介面依賴。

  • 分開存放:優點是每個介面可使用不同的版本並且使用方只需要依賴特定的介面。缺點是會產生更多的元件倉庫,元件數量也會增加依賴查詢的耗時。
    所以大型專案選擇分開存放的方式管理介面相對更合適一點。也可以考慮將大部分最核心的服務介面放置到一起管理。

支援Swift的服務介面實現推薦

使用Swift實現傳統的服務介面模式通常會遇到以下兩個問題:

  • 介面需要同時支援Objective-C和Swift呼叫,同時希望使用Swift特性設計API。如何實現Objective-C和Swift協議可以複用一個例項

  • Swift對於動態性支援比較弱,純Swift類無法支援執行時動態建立只能在註冊時建立例項

基於以上問題,個人推薦使用下面的方式實現介面服務模式:

  • 使用Objective-C協議提供最基礎的服務能力,之後建立Swift協議擴充套件提供部分Swift特性的API
  • 介面實現類繼承NSObject支援執行時動態初始化


















// @objc協議@objc public protocol JDCartService {    func addCart(request: JDAddCartRequest, onSuccess: () -> Void, onFail: () ->) }// swift協議public protocol CartService: JDCartService {
   func addCart() async
   func addCart(onCompletion: Result)
}
// 實現類class CartServiceImp: NSObject, CartService {    // 同時實現Objc和Swift協議}

服務應該中心化註冊還是分散式註冊

中心化註冊是在宿主APP啟動時統一註冊服務介面的對應實現例項,分散式註冊是在元件內元件自身進行註冊。個人推薦中心化註冊的方式在宿主APP啟動時統一進行註冊管理,明確服務的實現方更清晰,同時避免不同元件包含同一個服務介面的不同例項導致的衝突。

4.3.3元件版本相容

謹慎使用常量、列舉、宏

因為元件編譯釋出的時候會生成二進位制庫,編譯器會將依賴的常量、列舉、宏替換成對應的值或程式碼,所以當後續這些常量、列舉、宏發生變更的時候,已生成的二進位制庫並不會改變導致打包的時候依然使用的舊值,必須重新發布使用這些值的元件才行。所以應當儘量避免修改常量、列舉、宏值,如果已知後續可能會變更的情況下應避免使用常量、列舉、宏。

基礎元件API向後相容

  • 對外API需保證向後相容,使用新增API的方式擴充套件現有能力,避免對原有API進行break change改動或移除

  • 使用物件封裝傳遞引數和回撥引數,避免對原有API進行修改

提示:特別是對於Objective-C這類動態呼叫的語言來講,打包構建時並不能發現呼叫的方法不存在、引數錯誤這些問題。所以我們應當儘可能避免現有方法的變更。同時也推薦更多使用Swift編譯器可以發現這些問題提示編譯錯誤。

減少釋出大版本

Cocoapods為例,元件釋出大版本會導致依賴此元件的所有元件都必須同時升級到大的版本重新發布,這樣會給元件使用放帶來極大的更新成本。所以元件應該減少釋出大版本,除非必須強制所有元件一定要升級。

優先選擇介面服務減少暴露View類

當只關注API提供的能力並不關注API提供的形態時儘可能透過API的方式來暴露能力。因為暴露介面方法相比檢視View,呼叫方只需要依賴介面方法相比依賴View類可以更小化的依賴,同時介面對於實現方未來擴充套件能力更靈活。以選擇使用者地址API為例,通常呼叫方並不關注實現方以彈窗的方式還是頁面的方式提供互動能力讓使用者選擇,只關注使用者最終選擇的地址資料。並且呼叫方不需要處理彈窗和頁面的展示邏輯使用起來更方便,也便於實現方之後修改互動方式。

  • 使用介面的方法




addressService.chooseAddress { address in
}

  • 使用View的方式






let addressView = AddressView()addressView.callback = { address in    ///}addressView.show()

避免使用Runtime反射動態呼叫類

應當儘量避免使用反射機制在執行時使用其他類,因為類的實現方不能保證這個類一直存在,編譯器也無法檢測出錯誤。某些基於AOP的功能可能會使用到這種動態反射能力,但是大部分場景應該儘量避免。

4.3.4第三方庫

第三方庫元件不允許依賴其他元件

4.4  質量保障


    

移動端APP元件化架構實踐圖4.

雖然前面講到了很多規範和原則,但是並不能保證這些規範和原則可以強制執行。所以需要在元件釋出和應用打包階段新增一些卡口安全檢測,及時發現元件化依賴問題避免帶入線上。

4.4.1CI檢查

元件釋出

在元件釋出時新增一個安全檢查,避免不符合依賴規範的元件釋出成功。通常可以新增以下依賴檢查規則:

  • 第三方庫不可依賴其他元件

  • 基礎元件不可依賴業務元件

  • 業務元件不可直接依賴業務元件

  • 元件間通常不可相互依賴

  • 不允許元件層級間反向依賴

版本整合規範

整合系統需要將特定需求和元件版本關聯到一起,打包時會根據版本需求自動加入對應的元件版本。避免開發同學直接修改元件版本引入不應該加入到版本的特性。

打包構建

在宿主APP打包時,提前檢測出介面服務存在的問題,避免帶入到線上。通常可以檢測以下問題:

  • 服務介面對應的實現類不存在

  • 服務介面對應的實現類沒有實現所有方法

  • 使用ObjC Runtime動態呼叫類和方法

  • 元件被依賴但是並沒有被使用到(基於程式碼依賴查詢)

線上異常上報

線上檢查可以幫助我們在灰度釋出的及時發現問題及時修復,通常可以發現以下問題:

  • 路由跳轉對應的頁面不存在

  • 介面服務對應的實現類不存在

  • 介面服務對應的方法不存在

可量化指標

可以透過一些指標來量化整個工程元件化的健康程度,以下列出常見的一些指標:

  • 基礎元件依賴數量

元件依賴的所有基礎元件總數,當依賴的基礎元件總數過高時應該及時進行重構。如果大量的業務元件都需要依賴非常多的基礎元件,那可能說明基礎元件的依賴關係出現了很大的問題,這時候需要對基礎元件進行最佳化重構:

  • 考慮使用介面服務對外暴露能力,元件層級需要提升

  • 考慮將部分能力拆分出為獨立的新元件
  • 業務服務依賴數量

業務元件對其他業務服務元件的依賴數量。當業務元件依賴了其他業務服務呼叫時也會造成隱式的耦合關係,依賴過多時應當考慮是否應該對外暴露可監聽變化的通知訂閱以訂閱觀察的方式替代主動呼叫

  • 錯誤依賴關係數量

錯誤的依賴關係應該及時最佳化改造。



05 
  一些常見的問題  


理解,首先 MCube 會依據模板快取狀態判斷是否需要網路獲取最新模板,當獲取到模板後進行模板載入,載入階段會將產物轉換為檢視樹的結構,轉換完成後將透過表示式引擎解析表示式並取得正確的值,透過事件解析引擎解析使用者自定義事件並完成事件的繫結,完成解析賦值以及事件繫結後進行檢視的渲染,最終將目

基礎元件應該直接暴露還是使用介面對外暴露

基礎元件應該直接使用標頭檔案API暴露還是使用介面間接暴露有時候很難權衡,但是可以根據一些特性來權衡選擇:

API直接暴露
  • 功能單一/依賴少 - 一些工具類,例如Foundation
  • API複雜 - API非常多如果使用介面需要抽象太多介面,例如網路庫、日誌
  • UI元件 - 需要直接暴露UIView的UI元件,例如UIKit

介面對外暴露
  • 可擴充套件性 - 基於介面可以靈活替換不同的實現,例如定位能力可以使用系統自帶的API,也可以使用不同地圖廠商的API

  • 減少依賴引入 - 降低使用方的接入成本,提高日常開發/元件釋出效率

  • 可插拔能力 - 對應的能力可移除,同時也不影響核心業務
    提示:這些以介面對外暴露的API還有一個優勢是可以抽象成容器化的API,形成統一的標準規範。使用方呼叫同樣的API,不同的APP可以提供不一樣的實現。

小專案是否應該做元件化

個人認為小專案也可以做元件化,需要關注的是需要做到什麼程度的元件化。通常來講越大型越複雜的專案元件化拆分的粒度更細元件數越多。對於小專案來講雖然早期做元件化的收益並不大,也需要適當考慮未來的發展趨勢預留一定的空間,同時也需要適當考慮模組間的依賴關係避免後期拆分模組時很困難。剛開始做粒度比較粗的元件化,之後在專案發展中不斷的調整元件化的粒度。也可以考慮使用類似Monorepo的方式來管理專案,程式碼都在一個倉庫中管理,透過資料夾隔離規範模組間的依賴。

單一工程如何改造為元件化工程

一般來講需要使用循序漸進逐步重構的策略對原有專案進行改造,但是有一些模組可以優先被元件化拆分降低整個元件化的難度:

  • 優先拆分出最核心的所有業務模組可能都需要使用的元件,這些元件拆分完成以後才能為之後業務模組拆分提供基礎。例如Foundation、UI元件、網路庫、圖片庫、埋點日誌等最基礎的元件。

  • 優先拆分不被其他元件依賴或被其他元件依賴較少的模組元件,這些模組相對比較獨立拆分起來比較高效並且對現有工程改造較小。例如效能監控、微信SDK這類相對獨立的能力。

元件化帶來的額外成本

元件化架構可能會帶來以下這些額外的成本:

  • 管理更多的元件git倉庫

  • 每次元件釋出都需要重新編譯/釋出

  • 由於元件使用方都是使用相應的元件二進位制庫,所以除錯原始碼會變的更困難

  • 開發元件管理平臺,管理元件版本、版本配置表等能力

  • 每個元件需要有自己的Example工程進行日常開發除錯

  • 處理可能存在的元件版本不一致導致的依賴衝突、編譯錯誤等問題

  • 需求可能會涉及到多元件改動,如何進行Code Review、版本合入檢查

Monorepo

個人並沒有在實際的專案中使用過Monorepo方式管理專案。Monorepo是將所有元件程式碼放在單個git倉庫內管理,然後使用資料夾拆分為不同的元件。不同資料夾中的程式碼不能直接依賴使用,需要配置本地資料夾的元件依賴關係,在實現元件化的同時避免拆分太多的git倉庫。不過個人認為Monorepo同時也需要解決以下幾個問題:

  • 編譯耗時最佳化 - 將所有原始碼放在單個工程中會導致編譯變慢,所以必須最佳化現有工程編譯流程,降低非必要的重複編譯耗時。

  • 元件版本管理 - 在元件化工程中可以透過配置元件的特定版本來管理功能是否合入到版本中,但在Monorepo中只能透過分支Merge Request來管理特性是否合入,回滾也會更加繁瑣。

  • 高質量CI流程 - 在單個倉庫中,當一個開發者有倉庫許可權時他就可以修改該倉庫的任意程式碼。所以必須完善程式碼合入規範,更高標準的Code Review、整合測試檢查、自動化檢查避免問題程式碼帶到線上。



06 
  總結  


理解,首先 MCube 會依據模板快取狀態判斷是否需要網路獲取最新模板,當獲取到模板後進行模板載入,載入階段會將產物轉換為檢視樹的結構,轉換完成後將透過表示式引擎解析表示式並取得正確的值,透過事件解析引擎解析使用者自定義事件並完成事件的繫結,完成解析賦值以及事件繫結後進行檢視的渲染,最終將目
可能並不存在一個完美的架構,大家自身的組織架構、業務、人員都在變動,架構也需要隨著這個過程進行適當的調整和重構,最重要的是要能及時發現架構中存在的問題並且有意願/能力去調整避免一直堆積變成更大的技術債務。

同時工程架構的改變也會一定程度的改變開發人員的分工,對於大型工程來講元件化的程度更高,每個開發人員的工作分工會更細。對於底層基礎元件的開發,需要提供更多高效能/高質量的基礎元件讓上層業務開發人員更加效率的支撐業務,技術深度也會更加深入。對於上層業務開發,更多是使用這些底層基礎元件,同時可能也需要掌握多種跨端UI技術棧快速支撐業務,技術棧會更廣但是不會太深入

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70027824/viewspace-2992974/,如需轉載,請註明出處,否則將追究法律責任。