微信支付的架構(gòu)真的那么牛嗎?
作者:方秋枋
背景
作為一個重要業(yè)務(wù),微信支付在客戶端上面臨著各種問題。其中最核心問題就是分平臺實現(xiàn)導(dǎo)致的問題:
iOS 和安卓實現(xiàn)不一致
容易出 Bug
通過溝通保證不了質(zhì)量
擴展性差,無法快速響應(yīng)業(yè)務(wù)需求
需求變更迭代周期長
數(shù)據(jù)上報不全面
質(zhì)量保障體系不完善
缺少業(yè)務(wù)及設(shè)計知識沉淀
協(xié)議管理松散
缺少統(tǒng)一的自動化測試
用戶體驗不一致
比如下圖就是之前安卓和 iOS 沒有統(tǒng)一前的收銀臺。
為了解決分平臺實現(xiàn)這個核心問題,并解決以往的技術(shù)債務(wù)。我們建立起了一整套基于 C++
的跨平臺框架,并對核心支付流程進行了重構(gòu)。
微信支付跨平臺從 iOS 7.0.4 版本起, 安卓從 7.0.7 版本起全面覆蓋。
線上效果指標(biāo)
以 iOS 上線情況為例:
Crash 率
上線前后 Crash 率保持平穩(wěn),沒有影響微信穩(wěn)定性,跨平臺支付無必現(xiàn) Crash,做到了用戶無感知切換。
舉個例子,大家可以用微信發(fā)一筆紅包,拉起的收銀臺和支付流程就是由基于C++編寫的跨平臺代碼所驅(qū)動的。
效能提升
以核心支付流程代碼為例,跨平臺需要 3512 行,iOS 原生需要 6328 行。減少了近 45% 的代碼。
以新需求開發(fā)為例:
7.0.4 版本需求一:收銀臺改版
7.0.4 版本需求二:簡化版本收銀臺
跨平臺實現(xiàn):iOS + 安卓 共計 3 人日,在封板時間前完成
原生實現(xiàn):iOS, 安卓封板時間后一周才基本完成
跨平臺實現(xiàn):iOS + 安卓共計 5 人日,在封板時間前完成
原生實現(xiàn):iOS, 安卓封板時間后一周才基本完成
那么支付跨平臺軟件架構(gòu)怎么樣有效進行質(zhì)量保障,并且提升生產(chǎn)力呢?這是這篇文章的主要內(nèi)容。
對基于 C++ 如何從零到一構(gòu)建跨平臺框架感興趣的同學(xué),可以在 https://github.com/100mango/zen/blob/master/Qcon2019/%E5%9F%BA%E4%BA%8E%20C%2B%2B%20%E6%9E%84%E5%BB%BA%E5%BE%AE%E4%BF%A1%E5%AE%A2%E6%88%B7%E7%AB%AF%E8%B7%A8%E5%B9%B3%E5%8F%B0%E5%BC%80%E5%8F%91%E6%A1%86%E6%9E%B6.key 下載我在 2019 QCon 廣州站的演講 《基于 C++ 構(gòu)建微信客戶端跨平臺開發(fā)框架》的 Keynote.
什么是軟件架構(gòu)
什么是軟件架構(gòu)?正如 Ivar Jacobson (UML 之父)說過的一樣,找五個人來回答這個問題,五個人可能都有各自不同的答案。
架構(gòu)定義可以有很多種說法,從代碼規(guī)范到發(fā)布流程都可以是架構(gòu)的一部分。
針對微信支付的業(yè)務(wù)特點,這里對架構(gòu)的定義是:架構(gòu)是系統(tǒng)的組成部件及其之間的相互關(guān)系(通訊方式)。這更符合我們程序員日常編寫業(yè)務(wù)代碼時對架構(gòu)的理解。也就是通俗意義上講的 MVC
,MVVM
等。
為什么需要軟件架構(gòu)
早在 1986 年的時候,人月神話的作者在討論軟件的復(fù)雜性時,談到:軟件的本質(zhì)復(fù)雜性存在于復(fù)雜的業(yè)務(wù)需求中。
而管理復(fù)雜性,最根本的手段就是職責(zé)分離。為了實現(xiàn)職責(zé)分離,代碼重用,架構(gòu)慢慢地復(fù)現(xiàn)出來。架構(gòu)的本質(zhì)是管理復(fù)雜性。
沒有架構(gòu),我們所有的代碼都耦合在一起,人類的心智模型不擅長處理這種復(fù)雜性,架構(gòu)的設(shè)立,和圖書館的圖書分類,公司的組織劃分等,本質(zhì)都是一樣的。是為了管理復(fù)雜性,以取得更高的生產(chǎn)力。
從零到一構(gòu)建支付跨平臺軟件架構(gòu)
在移動客戶端領(lǐng)域,業(yè)界基于 C++
來編寫業(yè)務(wù)代碼,并沒有成熟的架構(gòu)。即使使用 C++ 編寫業(yè)務(wù)邏輯,但都不涉及 UI,不涉及界面的跳轉(zhuǎn)流程。
既然業(yè)界沒有一個成熟的架構(gòu)可借鑒,那么是不是直接把業(yè)界通用的架構(gòu)簡單套用一下就好?
1. 抽象業(yè)務(wù)流程
現(xiàn)在業(yè)界通用的有 MVC , MVP, MVVM 。這些大家都熟悉的軟件架構(gòu)。但是這些軟件架構(gòu)都存在一個問題: 那就是沒有處理好業(yè)務(wù)流程, 界面轉(zhuǎn)場。
微信支付的流程多。而流程就是由一個個的界面(ViewController,Activity)和相關(guān)的業(yè)務(wù)邏輯組合而成。
上面的 MV(X) 模式忽略了一個非常重要的一點,那就是業(yè)務(wù)流程,界面的轉(zhuǎn)場究竟由誰負責(zé)。也即 ViewController 與 ViewController 之間的關(guān)系由誰維護,業(yè)務(wù)流程的邏輯寫在哪里。如果還按照傳統(tǒng)的 MVC 模式,那么 ViewController 自己負責(zé)和不同的 ViewController 通訊。那么 ViewController 得不到復(fù)用,更致命的是業(yè)務(wù)流程的代碼非常不清晰,業(yè)務(wù)流程的代碼都被分散到各個 Controller 中, 而一個 Controller 又可能耦合了多個業(yè)務(wù)的代碼。
舉個例子:一個普通的轉(zhuǎn)賬流程,可能會涉及風(fēng)控攔截,實名驗證, 收銀臺, 綁卡,支付成功頁等等。如果是基于 MVC
這種架構(gòu)的話,很快代碼會變得難以維護。
因此,為了適應(yīng)微信支付流程多,界面跳轉(zhuǎn)復(fù)雜的特點。架構(gòu)抽象的第一步就是將業(yè)務(wù)流程抽象為一個獨立的角色 UseCase
。同時, 把界面抽象為 UIPage
。 一個大的業(yè)務(wù)流程可以分解為一個個小的業(yè)務(wù)流程。
和剛才基于 MVC 混亂的架構(gòu)相比:
業(yè)務(wù)流程的代碼能夠聚合到 UseCase 中,而不是分散到原來 iOS, 安卓的各個 ViewController,Activity 中。
業(yè)務(wù)流程和界面得到了復(fù)用。
契合微信支付多流程,界面跳轉(zhuǎn)復(fù)雜的業(yè)務(wù)特點。
2. 加入路由機制
既然流程得到了抽象,這個時候需要針對業(yè)務(wù)流程做更深的思考。在開發(fā)支付業(yè)務(wù)流程時,開發(fā)者不可繞過的問題有:
流程之間,頁面之間的流傳。
比如我們要給一個朋友轉(zhuǎn)賬,輸入金額,確認支付,觸發(fā) Cgi 后。下一個流程是多變的。有可能用戶需要去實名,有可能用戶要進入一個安全攔截的 WebView,或者是正常拉起收銀臺。
本文中的名詞
CGI
可以理解為一個網(wǎng)絡(luò)請求,類似HTTP請求。那么以往在 iOS, 安卓分開實現(xiàn)時,都沒有一個統(tǒng)一的處理機制。要么就是通過網(wǎng)絡(luò)回包的某個字段來判斷,要么就是本地維護一些狀態(tài)來決定下一步走什么流程等等。非常繁瑣,易錯。
特殊流程的處理
支付業(yè)務(wù)流程還有個特殊的地方,那就是在正常流程的中間,往往很多時候要需要插入一些特殊流程。比如有些地方要跳轉(zhuǎn) Webview, 有些地方要跳轉(zhuǎn)小程序,有些地方要彈窗告知用戶風(fēng)險,或者終止當(dāng)前流程,等等。我們經(jīng)常需要在業(yè)務(wù)代碼里面不斷重復(fù)增加這樣的處理。
這些問題,引導(dǎo)我想到,微信支付需要一個路由機制。
首先了解一下路由機制。
路由機制的核心思想,就是通過向路由傳遞數(shù)據(jù),然后路由解析數(shù)據(jù),并響應(yīng)。
結(jié)合微信支付和網(wǎng)絡(luò)密切相關(guān)的特點。創(chuàng)新地將支付領(lǐng)域模型作為傳遞的數(shù)據(jù)。
那么怎么建立這個支付領(lǐng)域模型的呢?
建模,就是建立映射。領(lǐng)域知識 + 建模方法 = 領(lǐng)域建模。那么這里的領(lǐng)域知識,就是對支付業(yè)務(wù)流程的理解。建模方法,我采用了 UML 建模。最終會落地為 Proto 協(xié)議供客戶端和后臺一起使用。
首先,微信支付業(yè)務(wù)特點就是和網(wǎng)絡(luò)密切相關(guān),流程和頁面往往是由 Cgi 串聯(lián)起來。因此建立模型時,最外層便是網(wǎng)絡(luò)回包。對于路由機制,這里我們只關(guān)心路由數(shù)據(jù)模型。
路由數(shù)據(jù)模型由 路由類型,還有各個路由類型所需要的信息組合成。
路由類型清晰的定義了要觸發(fā)的行為。究竟是要開啟一個 UseCase,還是要打開一個界面,或者 網(wǎng)頁,小程序,彈窗等等。
然后就是這些行為所需要的數(shù)據(jù)。比如打開小程序所需要的參數(shù),彈窗所需要的參數(shù)等。
建立支付領(lǐng)域模型后,我們路由的解析就變得非常清晰了。路由解析之后,會根據(jù)路由類型,觸發(fā)不同的動作。
比如流程,界面流轉(zhuǎn),會交給 UseCase 處理。
而特殊流程,比如打開小程序,打開 webview, 彈窗這些行為會統(tǒng)一進行處理。
我們在第一步把業(yè)務(wù)流程抽象為 UseCase。第二步則加入了路由機制。
加入路由機制后,支付跨平臺的軟件架構(gòu)演進為這個樣子。
加入路由機制后,對比 iOS,安卓原來的舊架構(gòu):
統(tǒng)一了流程,頁面的流轉(zhuǎn)。清晰,易維護。
統(tǒng)一了特殊流程的處理,減少重復(fù)工作。
在加入路由機制的時候,結(jié)合微信支付和網(wǎng)絡(luò)密切相關(guān)的特點進行了支付領(lǐng)域建模。支付后臺協(xié)議重構(gòu) 2.0 的核心思想也是圍繞著這個路由機制展開。
再來看一下,加入路由機制后,對生產(chǎn)力的提升。以支付流程打開 WebView, 小程序為例,減少將近 83% 的代碼。更重要的是,這里的特殊流程,是在路由機制里面統(tǒng)一處理的,沒有耦合到業(yè)務(wù)代碼中,并且是可復(fù)用的。
3. 管理網(wǎng)絡(luò)請求
首先看看原來 iOS 處理支付網(wǎng)絡(luò)請求的缺陷:
原來支付的請求,都是通過一個單例網(wǎng)絡(luò)中心去發(fā)起請求,然后收到回包后,通過拋通知,或者調(diào)用閉包的方式回調(diào)給業(yè)務(wù)側(cè)。
會存在這樣的問題:
CGI 一對多通訊問題。
舉個之前遇到的問題。
那么錢包發(fā)起的 Cgi 的回包就會覆蓋收付款頁面的數(shù)據(jù)。之前在 iOS 只能通過修修補補,增加場景值,增加些標(biāo)記位來解決??赡苣骋惶炀蜁殖霈F(xiàn)新的坑。
進入錢包頁面后,發(fā)起了一個 Cgi
然后進入收付款頁面也發(fā)起同一個 Cgi.
如果收付款發(fā)起的回包先到
然后錢包首頁的回包再到。
CGI 生命周期問題。
不時會有用戶反饋一下,怎么沒有做什么操作,突然就會彈出網(wǎng)絡(luò)報錯。
原因就是 Cgi 的生命周期有問題,在業(yè)務(wù)結(jié)束后,Cgi 的回包仍然得到了處理。
解決方案:
將 Cgi 抽象為獨立對象
在架構(gòu)設(shè)計上來說,舊架構(gòu)是通過單例模式實現(xiàn)的集約型 API,而我們新的架構(gòu)則是通過命令模式實現(xiàn)的離散型 API。
也就是將 Cgi 封裝為獨立對象。我們把 Cgi 相關(guān)屬性和能力內(nèi)聚起來。開發(fā)業(yè)務(wù)時,只需簡單繼承 BaseCgi,設(shè)置一下參數(shù)即可。
劃分職責(zé),明確生命周期
關(guān)于 Cgi 由誰發(fā)起,之前安卓和 iOS 都沒有一個統(tǒng)一的做法。有些人會放到 Activity,ViewController,和 UI 代碼耦合起來。
因此,在跨平臺軟件架構(gòu)中,我們統(tǒng)一由業(yè)務(wù)流程 UseCase 進行發(fā)起。并且生命周期是一對一的,一個 Cgi 只會有一個 UseCase 處理, UseCase 銷毀后,Cgi 也隨之銷毀。
對比舊架構(gòu):
杜絕了一對多通信造成的 Bug
生命周期和業(yè)務(wù)邏輯綁定,不會出現(xiàn)業(yè)務(wù)結(jié)束,Cgi 回來后再觸發(fā)動作。
高內(nèi)聚,低耦合。將 Cgi 相關(guān)的數(shù)據(jù),能力集中處理,業(yè)務(wù)側(cè)無需感知。
提供統(tǒng)一的緩存,加密能力。
第一步和第二步,我們抽象了業(yè)務(wù)流程,加入了路由機制。
在第三步管理網(wǎng)絡(luò)請求后。我們的軟件架構(gòu)演進為這樣子。
4. 規(guī)范數(shù)據(jù)傳遞
iOS 和安卓的舊架構(gòu)都存在信息傳遞不當(dāng)和數(shù)據(jù)污染問題。這個問題最嚴重。iOS 和 安卓都出過不少 bug。
首先我們來看看最近現(xiàn)網(wǎng)出現(xiàn)過的問題:
之前 iOS 出現(xiàn),不少內(nèi)部同事,外部的用戶都在反饋:進行零錢頁后,會無故彈空白框。而支付又和金錢有關(guān),引起用戶的恐慌。
具體原因就是:
進入支付首頁時,后臺返回了數(shù)據(jù),然后被寫入到一個公共的 Model.
然后進入錢包頁,再進入零錢頁。這個公共 model 一路被傳遞過去。
然后零錢頁讀取了公共 Model 的數(shù)據(jù),但是代碼無法處理,導(dǎo)致出現(xiàn)了這個讓用戶恐慌的問題。
除此之外,之前還有有很多發(fā)生在安卓,iOS ,像錢包頁零錢展示錯誤。付款的時候。銀行卡失效等等問題。
這些問題五花八門,看起來發(fā)生的地方,場景都不一樣。每次遇到這類問題的時候,就只能去修修補補。
但是深究下去,會發(fā)現(xiàn)真正的原因,是軟件架構(gòu)上存在的問題:
支付舊的架構(gòu)采用了黑板模式,雖然方便了數(shù)據(jù)讀寫。但是帶來的問題和收益完全不成正比:
存在公共讀寫的數(shù)據(jù)類型。
安卓傳遞的數(shù)據(jù)類型是一個字典,而 iOS 則是一個 Model 對象。所有的界面,業(yè)務(wù)邏輯都共用一個數(shù)據(jù)。
無序的數(shù)據(jù)流動。
數(shù)據(jù)的流動是不可追溯的,數(shù)據(jù)的修改可以發(fā)生在任意使用公共數(shù)據(jù)的地方。
那么支付跨平臺軟件架構(gòu),為了杜絕這樣的問題。我是這么做的:
去掉公共讀寫的數(shù)據(jù)類型
傳遞值類型(Value Type)的數(shù)據(jù), 后面流程修改數(shù)據(jù)時,不影響前面的流程。
單向傳遞數(shù)據(jù),只依賴注入必要數(shù)據(jù)。
如果數(shù)據(jù)修改需要通知前序流程,使用代理模式通訊。
規(guī)范數(shù)據(jù)傳遞后。對比舊架構(gòu):
從架構(gòu)上根本解決了困擾微信支付已久的數(shù)據(jù)污染的問題。
數(shù)據(jù)的流動變?yōu)閱蜗颍瑪?shù)據(jù)流動變得可追溯。
前面三步,我們抽象了業(yè)務(wù)流程,加入了路由機制,統(tǒng)一管理網(wǎng)絡(luò)請求。
那么規(guī)范數(shù)據(jù)傳遞后,我們軟件架構(gòu)就演進為這樣子。
總結(jié)
軟件的本質(zhì)復(fù)雜性存在于復(fù)雜的業(yè)務(wù)需求中。而軟件架構(gòu)的本質(zhì)就是管理復(fù)雜性,因此真正的好的架構(gòu),正是在復(fù)雜的業(yè)務(wù)需求中反復(fù)提煉和總結(jié)歸納而來,解決了真正的業(yè)務(wù)問題,不是空談。
軟件架構(gòu)除了清理歷史舊架構(gòu)的缺陷,是我們業(yè)務(wù)開發(fā)的基石之外。還能夠賦能業(yè)務(wù),為業(yè)務(wù)帶來價值。在建立軟件架構(gòu)的基礎(chǔ)上,還圍繞著軟件架構(gòu)建立起微信支付的跨平臺自動化數(shù)據(jù)上報機制,防重復(fù)支付,安全橫切等帶來巨大業(yè)務(wù)收益的能力。有機會的話,后面也會進一步編寫相關(guān)文章和大家交流探討。
特別推薦一個分享架構(gòu)+算法的優(yōu)質(zhì)內(nèi)容,還沒關(guān)注的小伙伴,可以長按關(guān)注一下:
長按訂閱更多精彩▼
如有收獲,點個在看,誠摯感謝
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺僅提供信息存儲服務(wù)。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!