導語
安居客 Android App 距離上次的模塊化/組件化重構已經兩年多了,重構之後很好地支撐了兩年多以來的業務發展。但這個世界總是在向前走的,沒有任何一種架構能夠一勞永逸地解決所有問題,外部環境的不斷變化相應地也要求項目架構做出改變,以此來應對環境變化所帶來的挑戰。
本文分享的就是我們安居客 App 團隊這次向平臺化轉型的背景、轉型過程中所面臨的問題、挑戰、我們的解決方案以及我們在這個過程中的收穫和感悟。
背景
自 18 年以來的市場環境大家都知道,各大公司都在縮減開支、提升組織效率,希望用更少的投入帶來更多的產出,以此來應對經濟下行帶來的不確定性。在這個大背景下,我們在 18 年底做出了一系列人效提升、業務整合的動作。
拿房產業務舉例:58 App 裡的房產業務之前是由北京的房產團隊開發的,而整個安居客 App 是由上海安居客團隊開發的。在這次調整之後,北京的房產團隊作為一條垂直業務線來負責 58 App 和安居客 App 中的租房業務的開發,安居客團隊則繼續負責整個安居客 App(包含新房、二手房、內容、IM 等業務)的開發,同時還要開發 58 App 中二手房、新房、房產大內容等業務。
這樣一次調整給我們帶來了三大挑戰:
1. 在人力不變,工作量翻倍的情況下,我們要如何保證業務的迭代速度不受影響?
2. 由以往的「單兵作戰」變成了跨團隊跨地域協作開發,我們要如何作為一個平臺方來和業務方協作開發?
3. 對於 58 App 而言我們是業務方,對於北京房產團隊而言我們是平臺方,我們要如何同時處理好雙重角色?
面對這些挑戰我們需要轉變開發思維、改進項目架構。
一次項目重構和架構升級,不只是要解決當下的問題,更要為未來一到兩年的業務發展提供支持。為了解決這一系列的問題,我們聯合 58 無線團隊、58 房產團隊、前端、後端多個團隊的同學一起發起了「木星計劃」,也開啟了我們的平臺化轉型之路。
問題、挑戰及解決方案
當公司的業務調整政策下來以後,團隊面臨的最急迫的一個選擇是繼續在 58 App 上維護老的房產代碼,迭代新功能;還是將安居客現有業務搬遷到 58 App 實現一套代碼在兩個 App 裡運行呢?
- 前者我們需要分一半的人力去開發 58 App,這必然導致團隊的需求消化量減半,業務迭代速度受影響。好處是暫時不用做任何技術上的改造。
- 後者需要我們協調 58 無線、58 房產、前後端等多個團隊一起對 App 做一次「大手術」,同時還需要說服產品團隊容忍改造期間業務迭代速度的放緩。好處是能夠一次性將安居客上的房產業務搬遷到 58 App 上,後續我們只需針對房產業務做一次開發就能跑在兩個 App 上,用一份人力做了兩份的活,開發效率翻倍。
為長遠計,我們最終選擇了後者。可是要做到一套代碼雙端運行並不容易,58 App 和安居客 App 隸屬於北京、上海兩個不同的團隊開發,大家底層庫不一樣,技術方案不一樣,開發模式也不一樣,要達到我們的目的必然要費一番功夫。接下來我從整體到局部逐步介紹我們在平臺化演進過程中的設計思路、遇到的問題和解決方案。
2.1 整體設計
所謂平臺化,就是安居客 App 要作為一個平臺來對平臺上承載的各種垂直業務提供服務,每個服務都需要對上層提供標準化的接口來支撐平臺上各類垂直業務的功能
這一次的項目重構除了要轉型平臺型 App 支持其他業務的接入和未來業務的發展,還有更重要一點是要做到一套代碼在 58 和安居客雙平臺運行。
要做到這一點,在現有的業務體系和代碼體量下雖然工作量巨大,但大的思路卻是很清晰簡單的:
- 底層庫能統一的儘量統一;
- 短時間內無法統一的庫以及平臺特性通過中間層屏蔽平臺差異。
安居客 App 架構調整
針對安居客 App ,我們需要調整架構,引入一個平臺中間層並針對平臺中間層接口做安居客 App 側的實現,並將垂直業務中原本調用平臺接口的地方改為調用中間層接口。同時將之前的業務組件層細分為「平臺級組件」和「業務級組件」,所有垂直業務不再依賴平臺級組件,只依賴平臺中間層和業務級組件,並明確業務代碼遷移的邊界。
58 App 架構調整
同時 58 無線團隊的同學也改進了他們的架構,和我們一樣引入了平臺中間層及中間層在 58 App 上的實現,保證安居客業務遷移進來後能正常編譯運行。
上面這幾張架構圖呈現了兩個 App 架構調整的大方向,具體做了哪些、遇到了哪些問題我在下面的小節裡一一介紹。
2.2 平臺中間層屏蔽底層差異
計算機領域的任何問題都可以添加一箇中間層來解決。58 App 和安居客 App 作為兩個不同的平臺,對業務層提供的能力是不一樣的,接口、方法名都是不一樣的。同一塊業務代碼要跑在兩個不同的平臺,必然要引入一箇中間層來抹平差異、屏蔽底層細節,同時對外提供統一的接口供業務層調用。
因此 58 無線團隊、58 房產團隊和我們安居客團隊三方協商,共同制定了一套平臺中間層 API,然後兩個平臺再針對平臺中間層 API 做具體實現。
為了便於後期管理、劃清代碼邊界,我們將平臺中間層服務進一步細化,根據平臺差異性將中間層劃分為平臺公共服務、安居客平臺特有服務、58 平臺特有服務等。以 Java Package 作為區分,劃分到不同的包結構下。一旦後期某一特有服務變成了公共服務,則將其往平臺公共服務遷移。
在實現上,平臺中間層會提供一系列 Service 接口供垂直業務調用,同時提供一個 PlatFormServiceRegistry 類用來註冊和獲取服務。
<code>public class PlatFormServiceRegistry {
...
/**
* 註冊服務
*/
private void registService(Class serviceInterface, Class extends IService> serviceImpl) {
if (serviceInterface != null && serviceImpl != null ) {
classMap.put(serviceInterface.getName(), serviceImpl);
}
}
/**
* 獲取服務
*/
privateT getService(Class extends T> service) { /<code>
\t\t\t\t···
IService instance = serviceImplMap.get(service.getName());
if (null == instance) {
try {
Class extends IService> serviceClass = classMap.get(service.getName());
if (serviceClass != null) {
instance = serviceClass.getConstructor().newInstance();
serviceImplMap.put(service.getName(), instance);
}
} catch (Exception e) {
Log.d(TAG, e.toString());
}
}
return (T) instance;
}
}
在使用方式上,平臺方首先要註冊服務
<code>//註冊平臺服務
PlatFormServiceRegistry.registeAppInfoService(AjkAppInfoServiceImpl.class);/<code>
業務方在使用服務的時候獲取到對應的 Service 就可以調用相關方法了。
<code>//使用平臺服務
IAppInfoService appInfoService = PlatFormServiceRegistry.getAppInfoService();
appInfoService.getAppName(context);/<code>
2.3 統一 HybridSDK 解決雙平臺 H5 交互問題
安居客 App 由於歷史原因,Android 和 iOS 在與 H5 的交互協議上有很多不一樣(雖然後期統一過 JSBridge 協議,但很多歷史遺留協議仍舊是不一致的),58 App 上的 Native 和 JS 交互協議更是和安居客不一致。為了解決這些問題我們需要 Android 和 iOS、58 和 安居客有一個統一的 Native 與 JS 的交互協議;同時為了兼容歷史協議,讓業務平穩過渡,我們還需要設計一套過渡方案。
在未實現協議統一的情況下,一個 H5 頁面要上 58 App 和安居客 App 兩個平臺,需要支持兩套協議,再加上安居客之前 Android、iOS 協議的不一致,一個 H5 頁面最多可能需要支持 4~5 套協議。
為了實現協議的最終統一,並且讓業務平穩過渡,我們引入了一套過渡方案。在保留兩個平臺現有協議和 JSBridge SDK 的情況下,58 無線團隊的同學設計並開發了一個全新的 HybridSDK,過渡階段三套協議並存,來不及調整的舊業務使用舊協議,新開發及本次要調整的業務使用新協議。
然後隨著業務的迭代,不斷廢棄兩個平臺的自有協議,最終走向統一。
2.4 統一路由協議、API 動態下發路由解決頁面交互問題
現有 App 內的頁面跳轉要麼是 intent 跳轉,要麼是寫死的路由跳轉。在這次的平臺化改造過程中,我們從 58 App 上也學到了很多東西,其中動態路由下發就是我們學習並引入到安居客 App 中的。
簡單地說就是 App 給各個頁面定義好路由協議,App 在調用 API 的時候返回結果中會包含當前頁面跳轉到下一頁面的路由協議,這就是所謂的動態路由下發。這樣做會帶來三個好處:
1. 可以做到完善的降級策略,比如當線上的房源詳情頁出現了大面積的 Crash,API 可以返回一個 H5 頁面的路由協議,臨時用 H5 的房源詳情頁替代;同時 App 上線 HotFix,等 HotFix 覆蓋率達到一定程度後 API 再改回下發Native 路由,跳回 Native 頁面;
2. 可以支持新功能的效果驗證,利用 H5 和 Native 自由切換這一特性,可以在不發版的情況下驗證新功能的數據效果。比如要上線驗證某個改動或者某個新功能的效果,之前需要 App 發版,現在只需要做一版 H5 頁面,API 直接路由到這個 H5,如果驗證下來效果好則可以開發對應的 Native 頁面,不好則可以再嘗試其他方案;
3. 可以支持平臺化改造過程中的灰度上線,這一點放到下一小節詳細說明。
2.5 灰度策略保證上線後的穩定性
我們這次的平臺化改造,無論對安居客 App 還是 58 App 來說都是一次「大手術」,術後能否保證線上的穩定性、業務數據不受影響是非常重要的。就拿把安居客業務遷移到 58 App 這件事來說,如果一股腦的用安居客遷移過去的房產業務代碼替代 58 App 內的房產業務代碼,就算我們在技術上做到了絕對的穩定,也難保業務數據不會受影響。
好在得益於上面提到的動態路由下發方案,我們可以做到在少量城市、少量用戶上做灰度,讓這部分人先試用安居客遷移過去的業務,其它用戶繼續使用老的房產業務,數據效果好再逐步加量,直到完全替代。這樣就能最大限度的降低影響,保證上線後的穩定性。
2.6 業務平移帶來的包大小問題
雖然在上一次的模塊化/組件化改造過程中我們對各項垂直業務做了拆分、解耦,但是各業務還是有很多重疊的業務,於是我們將這些業務下沉到 CommonBusiness 組件中,同時這個 CommonBusiness 組件裡還包含了一些 App 平臺級別的基礎功能,因此這個 CommonBusiness 組件成了個大而全的東西。
在這次的架構調整中,我們為了實現業務的快速平移,將 CommonBusiness 和新房、二手房等幾條垂直業務線的代碼一股腦的遷移進了 58 App,這就直接導致了 58 App 體積的快速膨脹。於是在後期,我們將 CommonBusiness 按能力拆分成了多個獨立的組件,並且分為了「平臺級組件」和「業務級組件」。平臺級組件屬於安居客平臺特有,不隨業務遷移;業務級組件屬於多個垂直業務公用的組件,隨業務代碼一起遷移到 58 App。這一點在前面的架構圖中有體現。
2.7 持續演進
前面介紹中間層的時候提到,還有一部分底層庫暫時無法統一,現階段是通過引入中間層來解決的。但從長遠來看,整個集團無線體系下,依賴的底層庫還是要走向統一。就拿分享組件來說,現階段安居客和 58 都是使用自己的 ShareSDK,然後中間層定義了一套分享接口,兩個 App 分別調用自己的 ShareSDK 來實現接口滿足業務的分享需求。為了進一步降低開發成本,避免重複造輪子,後期雙方還需要統一使用同一個 ShareSDK,拋棄中間層。最終整個集團體系下所有的 App 的架構應該如下面這張圖所示:
統一的平臺層,不同的宿主搭配上不同的垂直業務,就是不同的 App。這一點還需要我們持續迭代才能做到。
收穫和感悟
整個木星計劃下來,有很多的收穫和感悟。說實話,這次的技術改造在技術上並沒有太大的難度,更談不上有什麼「黑科技」。改造能順利落地,更多的是對於全局的把控、資源的協調溝通以及各個兄弟團隊的積極配合。所以這裡拋開技術不談,談談以下幾點收穫:規範化、流程化和全局視角。
3.1 規範化、流程化
安居客 Android App 團隊在技術上過往基本都屬於小團隊作戰,很少有跨團隊、跨地域協同開發的經驗,也缺少和集團其它團隊的交流,因此規範和流程一直是我們團隊所欠缺的。
在之前的小團隊開發模式下,就這麼一畝三分地,想怎麼玩都行,怎麼方便怎麼來。但是這種方式一旦涉及到跨地域跨團隊的協作時就會遇到瓶頸。
比如北京租房團隊的需求開發完要集成進安居客,按照我們單純的想法,定個時間點提交代碼給我們就 OK 了,但實際情況是這種方式根本行不通。業務方什麼時候開發完、測試完、什麼時候交付給我們?業務集成的交付標準是什麼?平臺方測試和業務方測試如何配合、如何交接?業務代碼是以源碼方式集成還是 aar 方式集成等等這些都是問題,需要有一套標準化、流程化的規範來約束各方的行為,這樣才能保證項目順利上線。
像上面這樣的例子還有很多,我這裡就不一一列舉了。
3.2 全局視角
正所謂「不謀全局者,不足謀一域」,本次平臺化改造過程中的另外一個重大收穫是讓我們意識到要以全局視角去看到問題。
前面提到我們在規範和流程上有很多欠缺,很多問題單從自身無法解決,這促使我們跳出自己的圈子來思考問題。我們做 App 開發的同學往往容易把視角侷限於自己負責的一個頁面、一個功能模塊、一個業務,能站在整個 App 的角度看待問題的已經寥寥無幾了,更別提跳出 App 的視角來看待問題。
但缺少全局視角,很多事情就不能很好地完成。舉個例子,假設你接到了一個優化頁面響應速度的任務,如果你單從 App 的角度入手你會發現能做的很有限,當你一通操作把 App 的優化點做完了卻發現中臺部門提供的 SDK 方法耗時兩秒,API 返回數據耗時三秒,真的是一頓操作猛如虎,一看結果二百五。
上面提到的這個例子還只是最常見、最基礎的一點。真正的全局視角是要求我們跳出 App 的限制,去思考整個研發流程的痛點、跨團隊協作上還有哪些優化空間等等,這樣才能真正提升我們的開發效率、產品性能和用戶體驗。就拿 APM 項目來說,如果只從 App 的角度來看,要實現對線上性能數據的採集會涉及到 Gradle Plugin、字節碼、ASM、數據的採集、存儲、上報、跨進程通訊等等,每一項都不簡單,每一項需要有一定的技術深度才能做好一個 APM 組件。那麼是不是每一項都做好了,實現了一個優秀的 APM 組件就能解決線上性能問題、提升用戶體驗呢?顯然不是!
如果你站在更高的角度來看,一個單純的 APM 組件並沒有辦法解決任何性能問題。我們需要從前期開發階段的開發規範和實現方案、QA 驗收標準、上線後的性能數據採集、數據上報後的聚合、分析和報警、性能問題的處理流程和規範等各個維度來協同處理,全方位地、系統地思考每一個點,從更高的角度入手才能真正解決線上性能問題。
登高望遠
現在我們把視角再往上拔高一個層次,站在整個技術團隊的角度來看移動開發。
安居客 App 是市面上一類比較典型的互聯網應用,它包含了基本的業務呈現、即時通訊、視頻播放、直播等面向用戶的功能模塊,也包含了數據採集、日誌記錄、網絡通訊等用戶看不見的功能模塊,同時還需持續交付平臺、測試平臺等等平臺的支持。
早期,即使在同一家公司這些內容各個 App 都是獨立的,各自自己做一套;
後來慢慢發現這種各自為戰的模式效率太低,重複造輪子的現象太嚴重,於是有了平臺化的概念。即時通訊是一個平臺、視頻直播是一個平臺、日誌系統又是一個平臺,持續交付、測試均是單獨的平臺。各個平臺為各種 App 提供不一樣的服務,各個 App 單獨對接各個平臺就可以了,避免了重複造輪子。
這種平臺化方案也有它自己的問題,通過前面的描述你會發現,基本上來一個新業務就要開發一個新系統,形成一個新平臺,這樣也會給 App 的接入帶來困擾,同時不同的平臺自然會有不同的團隊,這之間的溝通協作成本是巨大的。於是更進一步把各種分散的平臺統一成一個更大的平臺,統一對各種 App 提供服務。
這就是整個 App 研發體系從蠻荒時代到平臺化,再從平臺到中臺的完整進化史。
這裡說的平臺化和文章標題裡的平臺化不是同一個概念,這裡的平臺化是指一套系統作為一個平臺為各種 App、Web、小程序等前臺應用提供服務;而文章標題裡的平臺化是指安居客 App 作為一個平臺來支持各類房產業務。
那麼現在我們再來看研發團隊的組織結構,就完全不一樣了。請看下圖:
當我們能站在這樣一個角度看團隊的組織結構、研發流程的時候,很多事就更容易理解了。比如中臺部門推出了一套日誌系統,各個前端團隊要不要替換掉自研的埋點庫,使用中臺部門的服務,我的看法是當然要。讓專業的團隊做專業的事,中臺為各個前端業務團隊賦能,無論是質量上還是效率上都會有極大的提升。同時這樣也便於對各業務線的用戶數據、行為做統一的聚合、分析、報警等等,然後進一步反哺業務。
這也是為什麼之前集團 TEG 團隊推出 WMDA(58集團埋點系統)後,我們要頂住巨大壓力在安居客內部推廣的原因。
寫在最後
平臺化改造能順利完成並非我們一個團隊的功勞,這得益於前後端、產品、測試、58 無線、58 房產等多個團隊積極的配合,就比如前面介紹的很多技術方案都是 58 無線團隊的同學提出並開發的,因此要在這裡說一聲感謝,我們從兄弟部門學到了很多。
這次平臺化改造的過程中涉及了太多的內容,其中每一個點都能拿出來單獨寫一篇文章,由於篇幅限制並不能在文中一一詳述。我們團隊在平臺化方面的實踐上還缺乏足夠的經驗,個人能力也有限,未能將細節很好的一一呈現。如果大家發現文章中的錯誤或者實現方案上的不完美,歡迎在評論區留言交流指正。
58集團 / 張磊 / 安居客用戶技術部 Android 團隊
閱讀更多 58技術 的文章