貨拉拉貨運iOS使用者端架構最佳化實踐

陶然陶然發表於2022-11-10

   一、背景

  在移動應用發展過程中,隨著團隊人員的擴大、業務複雜化,程式碼量隨之增多,從而帶來了團隊協作開發中各種各樣的問題:

  功能程式碼之間的依賴複雜,可維護性差。

  協同開發過程中,並行開發存在相互依賴的情況。

  功能界限不清晰,基礎模組變動,會導致上層業務受到影響。

  一個合理的架構設計可以解決大型專案跨團隊協作分工和多業務線並行開發的效率問題,貨運iOS使用者端當前處理這樣一個低效率的問題上,需要成立一個技術專項,解決老的架構帶來的問題。最終達到降本增效,提升多人協作的開發效率。

  當前貨拉拉貨運iOS使用者端在開發中存在如下三大痛點:

  [Pod元件版本管理問題]

  pod元件版本依賴關係管理混亂:有的pod庫版本鎖定在podspec裡,有的在podfile裡。如遇升級某個pod元件,需要改N多個地方。在日常的開發過程中經常性因pod版本依賴關係不一致引起pod install/update 失敗,需要各同事排查問題,影響業務迭代的開發效率。

  [元件路由問題]

  當前使用者端的模組路由方案為基於Target-Action模式的CTMediator,此方案有如下幾個問題:

  呼叫元件名、方法標識採用字串傳入方式,散落到業務程式碼的各處,不便於維護。

  元件入口統一使用集中路由介面,透過使用整數標識不同的元件方法名,不便於問題的定位與查詢。業務同學在呼叫處,無法理解當前呼叫的數字所代表的業務邏輯。

  路由元件沒有對異常進行防護和線上監控,無法監控到模組元件呼叫的線上執行時情況。

  [多倉庫程式碼提交問題]

  貨運iOS使用者端有四大倉庫,Huolala/User/Move/CommonModule,各倉庫內的功能模組劃分不明確,業務功能與基礎功能混亂,導致某個需求迭代開發,會在3個或4個倉庫拉分支開發/提交/MR,在不具備完善的DevOps工具鏈的情況下,極其的影響開發的效率。

  為解決上述問題,由此我們制定瞭如下最佳化目標:

  目標一:提高研發效率, 包括:業務功能程式碼開發、編譯打包測試

  目標二:提高專案框架穩定性和可擴充套件性,減少線上事故發生的機率

  目標三: 解決目前開發中的三大問題 。

   二、相關的預研

  iOS貨運使用者端工程架構現狀梳理

  從工程架構出發,我們梳理了目前階段的架構,如下圖所示:  

  從關係圖可以看出,現行的架構很不合理,除了與基礎庫之間隔離,各業務之間互相依賴,耦合非常嚴重

  業務模組中包含了工具元件,並未下沉到公共模組中,重複性的工具程式碼冗餘在各業務程式碼中。

  公共模組中包含了部分業務相關的模組,未上浮到獨立的業務層中,導致上下級互相依賴、呼叫,破壞了穩定依賴原則

  iOS業界構架演進目標

  元件化為基礎的架構目標

  介面與服務分離,對業務分層

  單倉多元件,對倉庫結構改進

  2.1 元件化實現原則

  2.1.1 、抽象化原則

  所謂"抽象化",就是指從具體問題中,提取出具有共性的模式,再使用通用的解決方法加以處理。越底層的模組,應該越穩定,越抽象,越具有高複用度。

  2.1.2、穩定依賴原則

  不同元件之間的依賴關係必須要指向更穩定的方向。任何一個我們預期會經常變更的元件都不應該被一個難於修改的元件所依賴,否則這個多變的元件也將會變得非常難以被修改。即不要讓穩定的模組依賴不穩定的模組, 儘可能的減少不穩定庫的依賴。

  2.1.3、業務模組真正解耦

  元件化並不是說你把工程的程式碼拆分成多少個pod就算完事了,要實現模組之間真正的解耦才算真正的元件化。如果模組之間還是透過標頭檔案引入互相呼叫程式碼,迴圈依賴,那麼和原本在一個大工程裡用資料夾管理沒有區別。要做到真正解耦,需要深入到模組程式碼裡,抽絲剝繭的分離和整合。

  2.2 元件化指導方針

  2.2.1、元件化第一步,剝離產品公共庫和基礎庫

  對基礎能力,比如網路請求、UI元件、裝置能力、WebView這些,完全的剝離到基礎能力層,進行獨立迭代,以二方庫形式進行開發與釋出。

  針對三方庫,再封裝一層(適配層),使業務專案不直接依賴三方庫,方便後續功能迭代或新方案的隨時變更,從而減少對主業務的影響。

  2.2.2、元件化第二步,獨立業務模組單獨成庫

  拆分粒度可以先粗後細,將相對獨立的元件拆分出來。

  在開發過程中,對一些獨立的模組,如:登入模組、賬戶模組等等,也可以封裝成元件,因為這些元件是專案強依賴的,呼叫的頻次比較多。

  另外,在拆分元件化的過程中,拆分的粒度要合適,儘量做到元件的獨立性。

  同時,元件化是一個漸進的過程,不可能把一個完整的工程一下子全部元件化,要分步進行,透過不停的迭代,來最終實現專案的元件化。

  2.2.3、元件化第三步,對外服務最小化

  在前兩步都完成的情況下,我們可以根據元件被呼叫的需求來抽象出元件對外的最小化介面。

  2.2.4、可獨立的工具或二方庫使用pod 私有源管理

  可藉助公司CICD 打包的能力,為我們的端上APP打包、釋出提供便利性。

  將使用者端沉澱的二方以及業務端共治的公共二方庫,使用打包平臺釋出能力進行管理。

   三、方案設計與制定

  目標拆解制定  

  第一步、元件庫依賴管理設計與實施,解決pod元件版本管理問題

  第二步、元件路由器方案的設計與實施,解決現行路由器方案低效率不規範問題

  第三步、元件化架構分層,解決元件未劃分明確層次,互相依賴網狀問題。

  第四步、單倉多元件方案設計與實施,將業務模組上浮,工具模組下沉。

  Pod元件依賴管理方案  

  建立一個新的pod元件PodDepeLock,每個子元件分別管理幾個大元件庫的依賴pod的具體版本號

  每個大的元件Example下的podfile依賴PodDepeLock下的對應子元件。

  所有貨運專案中二方庫間的依賴不鎖定版本,由PodDepeLock統一處理。

  元件路由方案設計

  3.1、方案設計目標

  元件呼叫介面文件化、視覺化,方便使用和維護。

  保持元件化解的偶特性,呼叫方無所依賴元件名,只面向元件介面程式設計。

  獨立的元件可單獨編譯開發,保持最小化的業務開發體驗。

  對於元件化方案的架構設計優劣直接影響到架構體系能否長遠地支援未來業務的發展,對App的元件化 不只是僅僅的拆程式碼和跨業務調頁面,還要考慮複雜和非常規業務引數參與的排程,非頁面的跨元件功能排程,元件排程安全保障,元件間解耦,新舊業務的呼叫介面修改等問題。模組化解耦需求的更準確的描述應該是“如何在保證開發質量和效率的前提下做到無程式碼依賴的跨模組通訊”。

  PS:當前所做的路由方案是為後續的元件化業務拆分做好鋪墊。

  3.2、SHSpringRouter方案設計思想

  基於protocol-class模式下的路由元件。包含三大模組:

  核心層包含路由註冊、服務實現建立。

  安全協議介面呼叫,用於保護未實現的協議方法呼叫

  執行時的路由日誌輸出,包含:日誌、監控、異常堆疊。

  基於Spring框架Service的理念,透過protocol-class註冊繫結關係來實現模組間的API解耦。

  各個元件以服務的形式存在,每個都可獨立,以達到相互解耦的目的。

  各個服務具體實現與介面呼叫分離。解決網狀依賴的問題。

  面向協議程式設計。即服務與服務呼叫都是基於服務協議進行配合,透過SHSpringRouter路由達到呼叫目的。

  3.3、SHSpringRouter路由器架構設計  

  SHSpringRoute:核心路由處理中臺,維護整個protocol-class的關係表

  SHSafetyService:元件服務實現基類,用於處理方法呼叫異常的問題。比如未實現某個協議的方法,卻被呼叫了。每個元件服務實現都可繼承此類。

  SHSpringReportLog:當前路由工具在執行時的內部日誌輸出,用於除錯分析以及線上執行情況收集。

  3.3.1 核心流程時序圖  

  3.3.2 工程元件化實施架構圖  

  路由介面層成立為獨立倉庫:做為一個元件呼叫中臺集合,每個API宣告為其它元件呼叫的介面,內部的實現透過protocol-class模式進行呼叫,完全隔離了對其它元件的依賴。

  元件間的呼叫不依賴服務實現層,透過Protocol進行黑盒呼叫,完全解耦。

  業務線下的每條業務線集合,可有一個或多個子分類元件實現,進行細化管理。

  3.4 元件路由逐步替換方案  

  第一步,確認好改造目標,分為兩個方向:元件介面改造,業務方呼叫改造

  第二步,對已有的元件老路由進行改造,建立一個新的重構分支,在多人開發下,在此分支上合併程式碼衝突

  第三步,對正在並行進行迭代需求分支,進行的新增老路由呼叫進行記錄,需要在迭代版本時合併到版本主分支中,其主要思路為進行中的業務需求分支建立對應的業務需求路由替換分支,每個單獨的業務需求路由分支可隨業務需求發包提測,其中業務同學也參與路由的替換開發工作。

  第四步,準備整合測試時,將所有業務路由分支往當前迭代總路由分支提MR,比如biz1-Router向6648-SpringRouter提MR, 確認所有業務路由分支都合併完成後,再向當前的業務迭代分支提MR。最終打包提測。

  元件化架構分層  

  一個好的分層,在依賴方向需是由上依賴下,不能形成網狀結構。各業務線能獨立開發、釋出、上線。為達到這個目標,將整個使用者端工程分為5個層次:

  殼工程:和所有以cocoapod管理的工程專案一樣,殼工程做為當前使用者端打包釋出,是整個專案工程裡最穩定的一層。

  業務線層: 使用者端的業務主要包含搬家業務、同城/跨城業務以及公共的地圖選扯三大業務,分別歸屬於不同的業務開發團隊負責。

  介面中臺層:將所有業務元件的介面進行集中管理,各元件之前呼叫,都以協議介面形式進行API的呼叫,完全切斷元件間的聯絡。這其中包括:業務線介面、基礎業務服務介面、基礎能力服務介面、SDK介面卡介面。

  服務層:基於服務協議介面的具體的元件實現,可為獨立的pod元件,也可為某個業務線下某個子pod,其中服務包含兩大類別:基礎能力服務和基礎業務服務。

  基礎層:提供最基礎的工具類或模組,包括自研的二方庫以及開源的三方庫,將一些共性的能力進行組合,設計成高度叢集功能包。

  單倉多元件方案

  5.1 分倉設計的整體思路  

  各業務團隊獨立負責倉庫業務的處理與釋出,所做變更不會影響到其它業務團隊。互動式協議介面除外,例如搬家團隊負責SHMove倉庫的管理工作,地圖團隊負責SHMap倉庫的管理工作,同城&跨城團隊負責SHUser倉庫的管理工作。

  Huolala倉庫用於管理打包相關,無業務相關的程式碼,可以保證基本無變動,App版本資訊更新除外

  Move倉庫用於搬家獨立業務,會以pod分支方式依賴到專案中來。

  Map倉庫用於地圖獨立業務,會以pod tag方式依賴到專案中來。

  User用於同城、跨城業務,會以pod分支方式依賴到專案中來。

  其它的二方、三方 pod庫,統一由PodDepeLock進行集中式統一管理。

  5.2 單倉多元件設計思路  

  主導思想:業務模組上浮,工具模組下沉

  業務倉庫(User.git)管理所有的業務或業務域的元件,將可抽離的工具屬於相關的模組、元件下沉到工具倉(CommonModule.git)中。將User做為一個獨立的大元件,用於對外提供元件介面能力,內部將細分為4個子元件,每個子元件並不只是以資料夾形式管理,而是以獨立元件模式進行整合,可獨立進行編譯、除錯開發。

  工具倉(CommonModule.git)管理所有的工具模組以及App的生命週期任務處理,各區分的模組是子元件形式在工具倉元件中進行劃分,並身不再是獨立的元件。

   四、結項收益

  1、專項歷程

  專項從2022.3月到2022.7月,經歷了5個版本的迭代,歷時4個月時間,共有6位同事間斷性的參與了專項開發。透過互相的配合,分工合作最終完成了重構最佳化。

  共完成34個元件128個元件方法的元件路由重構工作,完成20個元件模組的倉庫元件遷移工作。共編寫85個測試用例,完成自測工作。

  2、結項收益

  2.1、Pod庫版本衝突問題降低

  在實施Pod版本依賴管理方案之前,經常性出現 Pod版本不一致導致失敗,需花費時間去排查報錯的問題。

  在實施此方案之後,基本沒有因Pod版本依賴衝突,阻斷開發的問題。

  2.2、現有路由器執行時效率提升50%

  Iphone6機型/Debug模式下的資料輸出

  原路由器SHMedium基於tager-action模式,在執行時的50次平均耗時 0.846ms

  現路由器SHSpringRouter基於Protocol-class模式,在開啟元件服務快取模式下50次平均耗時0.418ms

  2.3、元件介面整合效率提升

  原基於Medium路由模式下的元件,呼叫元件名、方法標識採用字串傳入方式,透過使用整數標識不同的元件方法名,不便於問題的定位與查詢, 無法理解當前呼叫的數字所代表的業務邏輯。

  整合或排查基本流程:

  找到要使用元件名字串

  透過查詢,定位到當前元件類名檔案。

  透過傳入方法數字識別符號,查詢元件類名中相應的方法

  觀察當前方法需要哪些引數

  最終完成元件的呼叫或業務邏輯的流轉。

  整個流程下來,需要花費1分鐘左右。

  重構後的基於SHSpringRouter元件,將介面和實現進行分離,每個整合分只需要關注協議介面API,即可完成程式碼開發的工作。

  整合或呼叫程式碼走讀流程如下:

  排查流程只需要點選方法,即可跳轉到實現處。

  呼叫API,只需要使用快速宏SHRouter(),即可完成元件API的呼叫。而且每個基於協議介面的API都有詳細的文件說明。

  整個流程下來,基本10s,就完成了。

  2.4、業務程式碼提交效率提升

  將業務相關的程式碼遷移到業務倉庫,讓開發同學只專注針對業務開發,不需要跨倉庫寫業務程式碼,節省了建分支、提MR的時間,提升整體的開發效率。

  2.5、無線上問題,各實施方案穩定執行

  整個專項共劃分為4期,經歷了5個迭代版本,完成共15個業務需求分支的程式碼合併。執行期間無線上相關的問題。實時監控和Carsh平臺無相關的異常上報。

   五、總結

  1、遇到的問題

  1.1、元件協議介面引數規範問題

  在進行元件重構過程中,元件協議介面的引數直接複用原元件的引數形式,但後續改造過程發現,這種固定的形參方式效率低,協議方式可以做得更簡潔明瞭,於是透過小組同學的討論和投票,制定了一套針對於元件協議介面的規範,包括介面命名、引數型別一系列的定義。

  1.2、單倉多元件模式下圖片資源獲取問題

  進行單倉多元件改造過程中,在單元測試階段發現SHKit工具庫提供的SHImageNamed("") 無法正常的獲取到圖片,透過對原始碼的梳理,發現是由於多層級元件路徑匹配引起的。

  考慮到工程有大量的模組都使用SHImageNamed(),決定對SHImageNamed()進行適配改造,修改正規表示式以及相關邏輯判斷後,得以正常獲取元件內的圖片資源。

  為防止後續開發會有圖片獲取異常,透過設定回撥代理,將圖片獲取時的異常上報到實時監控上,能夠發現線上的問題。

  1.3、業務迭代,分支合併問題

  在對SHUser倉庫和SHCommoudle倉庫程式碼遷移時,發現後續與各業務迭代需求進行程式碼合併是個極其頭痛的事,比如一個元件本來是在SHCommoudle倉庫,可現在移動到了SHUser倉庫裡,這種程式碼合併沒法透過git方式進行,需要人工的進行程式碼的合併,極其容易出錯。後續透過分析並整理了業務分支程式碼合併流程,與各業務同學共同一起合併程式碼,完成了迭代需求的上線。

  1.4、監控體系建設

  路由替換與單倉多元件改造是兩個風險係數極高的任務,我們透過監控體系以及大量單元測試用例保持上線的質量、減少重構帶來的風險。

  2、不足與思考

  專項前期規劃不足,技術預研時間跨度有點長

  拆分的元件沒有進一步的細化和抽離

  考慮到專項時間週期和業務穩定性,元件進一步的抽離將放到後續需求迭代版本中由業務同學進行。

  對於細化的目標,粗粒度如何、怎麼衡量,還沒有統一把控度,後續將根據業務元件本身的複雜度以及分離程度進行劃分。

  元件間路由通訊目前沒有解決模型傳遞的問題

  原設計是想透過通用模型,解決不同元件間引數傳遞因模型引發元件耦合的問題。但實際操作中發現通用模型帶來了其它的問題:通用模型臃腫,介面引數傳遞不明確。

  對於此問題還需進一步研究更好的方案。如讀者有更好的建議,歡迎評論指導。

  專案中還存在一些的Common模組

  不要讓Common出現以及怎樣在業務程式碼設計中避免陷入到Common陷阱裡尚未想好處理方案,如讀者有更好的建議,歡迎評論指導。

來自 “ 貨拉拉技術 ”, 原文作者:Sherwin.Chen;原文連結:http://server.it168.com/a2022/1110/6773/000006773887.shtml,如有侵權,請聯絡管理員刪除。

相關文章