哈囉出行小程序架構演進之路

 今天跟大家分享一下哈囉出行支付寶小程序的架構演進之路。

    內容主要分四個部分。一是對支付寶小程序的簡單介紹和我們的項目背景,二是我們遇到的問題和解決問題的思考,三是我們爲了解決這些問題引出的哈囉小程序新架構,四是對未來的展望。

一、小程序介紹及背景

    接觸過支付寶小程序的同學可能不是太多,不過不要慌,你完全可以把支付寶小程序當成微信小程序,它們有非常相似的架構。都有一個大而全的 IDE,如圖,左中側是一個類似 VS Code 的編輯器,右側是小程序模擬器,頂部是配置和操作區域。通過這個 IDE,開發者可以完成開發過程中的閉環操作。
小程序是一種特殊的網頁應用,有一個至關重要的全局配置,用以聲明小程序包含哪些頁面、配置全局默認標題和樣式等。配置頁面時要直接聲明頁面文件的目錄,不支持別名等功能。

二、問題和思考

    在理想情況或較爲簡單的業務場景下,開發、發佈流程比較簡單。在快速迭代、敏捷開發和 git flow 開發規範的情況下,開發者需要爲一個版本創建一個 release 分支,從 release 分支切出一個功能對應的 feature 分支進行開發,開發完成後合併代碼,並使用 release 分支代碼發佈體驗碼進行測試,測試完成後使用此體驗碼進行發佈。
但是這種紙上談兵的模式是完全無法支撐哈囉出行小程序這種中大型規模小程序的開發的。實際情況是這樣的:

    哈囉出行小程序有十幾個業務線同時在開發,對應十幾個需求、幾十位開發、幾十個 feature 分支。複雜度到達一定量級後小程序的開發發佈流程會成爲災難。比如人多後很容易同時在修改同一個文件或者分支間提交同步不及時從而引發 git 衝突問題,git 衝突問題是無法自動化解決的,非常依賴開發者對沖突代碼的理解,大型的衝突很難得到妥善解決。沒有什麼 git 操作是比處理衝突更讓人心情糟糕的,如果有,那就是…… 這邊先賣個關子,後面我們會提到。再比如一個需求開發完成後,開發者需要生成體驗碼給測試同學測試,但是小程序的體驗碼是同一時間只能存在一個的,新的體驗碼會讓舊體驗碼失效,而且體驗碼的發佈是沒有集中機制的,任何開發者都有權限通過上述 IDE 的操作區發佈體驗碼。這些原因會造成體驗碼的使用不便,你不知道這個體驗碼有沒有失效、是誰發佈的、對應哪個分支的代碼、有沒有你要測試的功能、最新的體驗碼在哪。
由此我們引出了兩個問題,「代碼分支衝突」和「發佈流程失控」。

    再來關注下我們的業務演變,這兩個圖分別是 2018 年 v4 版本和 2019 年 v5 版本的首頁截圖。

                             

    可以比較明顯地看出,首頁的業務 tab 從只有兩個快速增加到超過五個。而實際上從 2018 年到現在的時間裏,哈囉出行的業務規模快速膨脹,業務線數從 2 個到 11 個,開發者數從 2 個到 20 多個。對於一個野蠻生長的項目來說,突然承接這麼多業務需求和功能,自然而然會出現業務代碼耦合的問題。需求和功能增加意味着頁面和組件的增加,也就是代碼量的增加,這對體積敏感的小程序來說也是不小的負面影響。不僅不利於線上的用戶體驗,還會增加開發調試階段開發者構建速度,拖慢構建效率。
這樣我們又獲得三個問題,「業務代碼耦合」、「小程序體積過大」和「編譯時間過長」。

    回顧一下所有的問題,我們將其分爲兩類。「代碼分支衝突」、「業務代碼耦合」、「編譯時間過長」屬於代碼及項目組織問題;「發佈流程失控」和「小程序體積過大」屬於發佈流程鬆散的問題。兩類問題有各自的解決方式。

    對於代碼及項目組織問題,我們的解決思路是分倉庫。哈囉出行支付寶小程序的最大特點就是參與的業務線特別多,複雜度也是由此產生的。因爲我們決定把每個業務線的代碼拆到單獨的倉庫裏。業務無關的代碼,比如小程序入口、框架及登錄和定位這種通用能力,還是保留在原來的基礎倉庫。自此形成了基礎倉庫爲平臺底座,業務倉庫爲可插拔模塊的組織架構。每個業務線被歸到獨立倉庫後,就可以充分利用 gitlab 倉庫的管理能力,從原先的只在唯一一個倉庫進行所有業務線的分支管理,分解成在業務線的小倉庫裏自行進行同業務的分支管理,從而拆解分離了業務域,降低了分支管理的複雜度。同時業務線之間的代碼權限也被倉庫隔離開,實現了代碼安全和數據安全。

    現在代碼是拆開了,但是運行時還是需要拼成一個完整的小程序的。那麼要怎麼做集成呢?我們主要考慮了兩個方案。一是 git submodule/git subtree,這個方案經過調研後很快被否決了,submodule 和 subtree 是相似的管理子倉庫的方案,但是它們指令複雜,學習成本太高,操作失敗沒有友好的提示,不適合我們這種人員變動比較頻繁的大型團隊。前面提到沒有什麼 git 操作比處理衝突更讓人心情糟糕,如果有那就是 submodule/subtree 了。第二種方案是 lerna,lerna 是近年來在開源社區比較流行的一個多包管理工具,知名的 babel、react 都在使用 lerna 管理倉庫。但它還是不合適,lerna 注重多 package(npm 概念上的包)架構的管理,而我們只是子倉庫,而非多個 package。而且 lerna 提供的功能合指令優先,定製化比較困難,無法實現我們想要的一些功能,所以最終也沒有選用 lerna。

    最終我們決定自研構建工具來實現代碼集成。有了自己的構建工具相當於增加了一層抽象層,增加了項目的自由度。有句話說得好,沒有什麼問題是不能通過增加一個抽象層解決的,如果有,就再增加一層。通過構建工具這層抽象層,我們實現了除了基礎和核心的倉庫代碼集成之外的很多功能和特性。其中比較重要的功能叫做增量構建。含義是能動態指定需要構建的業務線。我們的業務線比較多,而一個業務線的同學通常只在本業務線內進行開發,我們小程序體積比較大,構建預覽起來比較慢,而且不支持也更新,每次保存代碼後都需要重新構建一次,開發過程較爲痛苦。而增量構建能實現僅構建平臺倉庫和一個業務線的代碼,從而減小小程序體積,加快構建預覽速度,提升開發體驗。

    在介紹增量構建原理前,我們先簡單理解一下分型的概念。分型是一個數學上的概念,表示宏觀、介觀或微觀尺度上,物理結構的相似性。在小程序的項目配置和目錄結構上,也存在着抽象意義上分型概念的存在,即小程序配置中聲明分包與頁面的嵌套結構與文件系統中業務倉庫與頁面代碼位置的嵌套結構是一致的。如果能同時動態控制兩個結構就能實現動態控制小程序的內容,這也就是增量構建的原理。

    我們來身臨其境體會下增量構建是怎麼使用的。開發者使用構建工具的命令行功能通過參數的形式指定要構建的對象,命令行工具拼裝參數傳遞給構建工具核心,構建工具根據參數有選擇地僅將構建目標的代碼移動到產物文件夾,並生成相應分型結構的小程序配置,最後開發者使用小程序 ide 打開產物文件夾。
構建工具也帶來一些小插曲。在開發中難免會有組件跨倉庫互相調用的情況,若依賴的組件沒有被構建進來小程序是無法運行的。對此我們在構建時分析依賴的組件所在的倉庫是否在本次構建列表中,如果沒有則只能移除對此組件的依賴。此外對於頁面跨倉庫跳轉時,我們對跳轉 api 加了一層 hook,運行時判斷目標頁面是否被構建進來,如果沒有則重定向到兜底的 404 頁面。

    構建工具真的有用嗎?我使用了 MacBook Pro16 GB 2133 MHz LPDDR3 1.4 GHz 四核 Intel Core i5 的測試機器進行了基準測試。全量構建的產物體積是 33.1MB,構建時間花費了 108.606 秒;相比之下最小構建的產物體積爲 6.72 MB,構建時間僅爲 22.652 秒。從這組數據中,我們可以輕鬆得出結論,一是小程序構建時間與代碼體積成正比,而是增量構建真的在節省我們開發的生命。
    

    分易合難。代碼分開了意味着無法直接通過引入依賴的機制進行通信,但開發中實現無法避免這種需求。考慮一種經典的情況,這是我們小程序首頁的宮格,如何實現在點擊宮格時既能實現業務邏輯又能解耦呢?最基礎最直接的方法就是用 if else,判斷點擊的是哪個業務線的宮格,引入對應業務線的處理函數並調用。但是我們知道在分倉庫的情況下是無法引入的,所以這種方案行不通。第二種方案是使用事件機制去點擊時分發,業務線各自進行事件監聽並處理。這個基於事件的方案思路方向是對的,但是也行不通。因爲首頁屬於平臺倉庫,還沒進到業務線,所以無法執行業務線進行事件監聽的代碼。爲了能成功且提前進行事件監聽,我們還是在構建工具這一層抽象層引入了一個特性——一套用於跨倉庫通信用的事件機制。
    

    我們約定了一個位於各倉庫根目錄下的特殊事件文件,在裏面書寫事件回調。構建時通過構建工具裏的事件處理模塊將特殊文件進行代碼分析和特殊處理後以文件生成和代碼生成的方式將事件監聽函數轉移至構建後的小程序響應位置,並添加好事件監聽。從而實現了沒有進入到子包也能執行業務線的事件監聽。


    

    這是上面提到的使用特殊事件文件監聽首頁宮格點擊的樣例,理論上這個文件存在於業務線倉庫的根目錄。觀察裏面的字段,‘pages/index/index’代表對這個頁面進行事件監聽,’gridClick’即約定的宮格點擊事件名,業務線可以在函數體內實現自定義的跳轉邏輯。從而達到了邏輯實現和代碼結構的解耦。

    我們再來回顧下問題。代碼及項目組織問題的三個問題中,分倉庫解決了「代碼分支衝突」的問題;新事件系統解決了「業務代碼耦合」的問題,增量構建解決了開發時「編譯時間過長」的問題。初次之外我們還使用構建工具支持了 less 和 ts 支持等提升開發體驗的功能。那剩下的屬於發佈鬆散的兩個問題怎麼辦呢,我們建立了一個集權的發佈平臺。

    發佈系統主要從權限、版本、體驗碼和體積四個方面約束規範發佈流程。權限是指開發者和體驗者的增刪,一般情況下只有小程序的管理員纔有權限在支付寶後臺管理。平時對人員權限變更的需求又比較頻繁,所以經常要麻煩管理員去操作。並且添加是根據支付寶賬號進行的,無法將支付寶賬號與具體某個同事對應起來。因此我們把這功能移植到我們新的發佈平臺,能夠相對自由、清晰地管理人員權限。版本方面,爲了強化版本意識,清晰地展示並控制發佈流程,我們在發佈平臺實現了一個發佈版本專題頁,表示現在處於哪個版本號、發佈分支是哪個、發佈負責人是誰、當前處於哪個發佈階段,從而降低了溝通成本。體驗碼的控制上,我們對體驗碼的發佈進行收口,支持了使用發佈系統在線發佈體驗碼,並引入了釘釘 bot 的推送能力,將生成的體驗碼連同一些基本的元信息和備註信息自動推送到體驗碼羣裏,而開發者通過 ide 發佈的版本不會被設爲體驗碼。從而實現了體驗碼發佈和使用的有序化。體積方面,我們都知道,前段項目的體積永遠是越小越好,小程序也有體積紅線。但是小程序是在提審時才告訴你體積是否滿足要求,而提審時發佈的最終一環,這時又需要重走發佈流程,比較麻煩。隨意發佈系統實現了小程序體積檢測機制,通過 git commit 觸發使用 webpack-bundle-analyzer 進行體積檢測和細緻的可視化分析,從而能夠在開發流程中就能發現體積隱患,提高發布效率。

    再回顧一下問題。發佈流程鬆散範疇內的兩個問題「發佈流程失控」和「小程序體積過大」通過發佈系統也解決了。

三、哈囉出行支付寶小程序新架構

    這樣我們聊完了第三部分,得出了我們哈囉出行支付寶小程序的新架構。即構建工具加發布系統,一套覆蓋開發、測試、發佈所有流程的互相配合的工具。
最後的展望部分,架構也需要架構演進。我們這一套架構誕生時面向的對象僅僅是我們自己的哈囉出行小程序項目,功能方面也在不斷地新增和完善。考慮到通用性,我們也希望在未來可以抹平這套架構項目上的特殊性,能讓別的支付寶小程序項目也能用上這套架構。甚至將目光放遠大些,讓不同平臺的小程序,如微信小程序、QQ 小程序、360 小程序、頭條小程序也能使用上這套架構。以此來回饋社區。

四、展望

    最後的展望部分,架構也需要架構演進。我們這一套架構誕生時面向的對象僅僅是我們自己的哈囉出行小程序項目,功能方面也在不斷地新增和完善。考慮到通用性,我們也希望在未來可以抹平這套架構項目上的特殊性,能讓別的支付寶小程序項目也能用上這套架構。甚至將目光放遠大些,讓不同平臺的小程序,如微信小程序、QQ 小程序、360 小程序、頭條小程序也能使用上這套架構。以此來回饋社區。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/MwM2l4O8pQukzMO_0EdKVg