1. 程式人生 > 其它 >微信支付的架構到底有多牛?

微信支付的架構到底有多牛?

背景

作為一個重要業務,微信支付在客戶端上面臨著各種問題。其中最核心問題就是分平臺實現導致的問題:

  1. iOS 和安卓實現不一致
  • 容易出 Bug
  • 通過溝通保證不了質量
  • 擴充套件性差,無法快速響應業務需求
  • 需求變更迭代週期長
  • 資料上報不全面
  • 質量保障體系不完善
  • 缺少業務及設計知識沉澱
  • 協議管理鬆散
  • 缺少統一的自動化測試
  • 使用者體驗不一致比如下圖就是之前安卓和 iOS 沒有統一前的收銀臺。

為了解決分平臺實現這個核心問題,並解決以往的技術債務。我們建立起了一整套基於 C++ 的跨平臺框架,並對核心支付流程進行了重構。微信支付跨平臺從 iOS 7.0.4 版本起, 安卓從 7.0.7 版本起全面覆蓋。線上效果指標
以 iOS 上線情況為例:

  1. Crash 率上線前後 Crash 率保持平穩,沒有影響微信穩定性,跨平臺支付無必現 Crash,做到了使用者無感知切換。舉個例子,大家可以用微信發一筆紅包,拉起的收銀臺和支付流程就是由基於C++編寫的跨平臺程式碼所驅動的。
  2. 效能提升
    以核心支付流程程式碼為例,跨平臺需要 3512 行,iOS 原生需要 6328 行。減少了近 45% 的程式碼。以新需求開發為例:7.0.4 版本需求一:收銀臺改版7.0.4 版本需求二:簡化版本收銀臺
  • 跨平臺實現:iOS + 安卓 共計 3 人日,在封板時間前完成
  • 原生實現:iOS, 安卓封板時間後一週才基本完成
  • 跨平臺實現:iOS + 安卓共計 5 人日,在封板時間前完成
  • 原生實現:iOS, 安卓封板時間後一週才基本完成

那麼支付跨平臺軟體架構怎麼樣有效進行質量保障,並且提升生產力呢?這是這篇文章的主要內容。

對基於 C++ 如何從零到一構建跨平臺框架感興趣的同學,可以在 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++ 構建微信客戶端跨平臺開發框架》的 Keynote.

什麼是軟體架構

什麼是軟體架構?正如 Ivar Jacobson (UML 之父)說過的一樣,找五個人來回答這個問題,五個人可能都有各自不同的答案。架構定義可以有很多種說法,從程式碼規範到釋出流程都可以是架構的一部分。針對微信支付的業務特點,這裡對架構的定義是:架構是系統的組成部件及其之間的相互關係(通訊方式)。這更符合我們程式設計師日常編寫業務程式碼時對架構的理解。也就是通俗意義上講的 MVCMVVM 等。

為什麼需要軟體架構

早在 1986 年的時候,人月神話的作者在討論軟體的複雜性時,談到:軟體的本質複雜性存在於複雜的業務需求中。而管理複雜性,最根本的手段就是職責分離。為了實現職責分離,程式碼重用,架構慢慢地復現出來。架構的本質是管理複雜性。沒有架構,我們所有的程式碼都耦合在一起,人類的心智模型不擅長處理這種複雜性,架構的設立,和圖書館的圖書分類,公司的組織劃分等,本質都是一樣的。是為了管理複雜性,以取得更高的生產力。

從零到一構建支付跨平臺軟體架構

在移動客戶端領域,業界基於 C++ 來編寫業務程式碼,並沒有成熟的架構。即使使用 C++ 編寫業務邏輯,但都不涉及 UI,不涉及介面的跳轉流程。既然業界沒有一個成熟的架構可借鑑,那麼是不是直接把業界通用的架構簡單套用一下就好?

1. 抽象業務流程

現在業界通用的有 MVC , MVP, MVVM 。這些大家都熟悉的軟體架構。但是這些軟體架構都存在一個問題:那就是沒有處理好業務流程, 介面轉場。微信支付的流程多。而流程就是由一個個的介面(ViewController,Activity)和相關的業務邏輯組合而成。上面的 MV(X) 模式忽略了一個非常重要的一點,那就是業務流程,介面的轉場究竟由誰負責。也即 ViewController 與 ViewController 之間的關係由誰維護,業務流程的邏輯寫在哪裡。如果還按照傳統的 MVC 模式,那麼 ViewController 自己負責和不同的 ViewController 通訊。那麼 ViewController 得不到複用,更致命的是業務流程的程式碼非常不清晰,業務流程的程式碼都被分散到各個 Controller 中, 而一個 Controller 又可能耦合了多個業務的程式碼。舉個例子:一個普通的轉賬流程,可能會涉及風控攔截,實名驗證, 收銀臺, 綁卡,支付成功頁等等。如果是基於 MVC 這種架構的話,很快程式碼會變得難以維護。因此,為了適應微信支付流程多,介面跳轉複雜的特點。架構抽象的第一步就是將業務流程抽象為一個獨立的角色 UseCase。同時, 把介面抽象為 UIPage。一個大的業務流程可以分解為一個個小的業務流程。
和剛才基於 MVC 混亂的架構相比:

  1. 業務流程的程式碼能夠聚合到 UseCase 中,而不是分散到原來 iOS, 安卓的各個 ViewController,Activity 中。
  2. 業務流程和介面得到了複用。
  3. 契合微信支付多流程,介面跳轉複雜的業務特點。

2. 加入路由機制

既然流程得到了抽象,這個時候需要針對業務流程做更深的思考。在開發支付業務流程時,開發者不可繞過的問題有:

  1. 流程之間,頁面之間的流傳。比如我們要給一個朋友轉賬,輸入金額,確認支付,觸發 Cgi 後。下一個流程是多變的。有可能使用者需要去實名,有可能使用者要進入一個安全攔截的 WebView,或者是正常拉起收銀臺。
    本文中的名詞 CGI 可以理解為一個網路請求,類似HTTP請求。
    那麼以往在 iOS, 安卓分開實現時,都沒有一個統一的處理機制。要麼就是通過網路回包的某個欄位來判斷,要麼就是本地維護一些狀態來決定下一步走什麼流程等等。非常繁瑣,易錯。
  2. 特殊流程的處理支付業務流程還有個特殊的地方,那就是在正常流程的中間,往往很多時候要需要插入一些特殊流程。比如有些地方要跳轉 Webview, 有些地方要跳轉小程式,有些地方要彈窗告知使用者風險,或者終止當前流程,等等。我們經常需要在業務程式碼裡面不斷重複增加這樣的處理。

這些問題,引導我想到,微信支付需要一個路由機制。首先了解一下路由機制。路由機制的核心思想,就是通過向路由傳遞資料,然後路由解析資料,並響應。
結合微信支付和網路密切相關的特點。創新地將支付領域模型作為傳遞的資料。那麼怎麼建立這個支付領域模型的呢?
建模,就是建立對映。領域知識 + 建模方法 = 領域建模。那麼這裡的領域知識,就是對支付業務流程的理解。建模方法,我採用了 UML 建模。最終會落地為 Proto 協議供客戶端和後臺一起使用。首先,微信支付業務特點就是和網路密切相關,流程和頁面往往是由 Cgi 串聯起來。因此建立模型時,最外層便是網路回包。對於路由機制,這裡我們只關心路由資料模型。
路由資料模型由 路由型別,還有各個路由型別所需要的資訊組合成。路由型別清晰的定義了要觸發的行為。究竟是要開啟一個 UseCase,還是要開啟一個介面,或者 網頁,小程式,彈窗等等。然後就是這些行為所需要的資料。比如開啟小程式所需要的引數,彈窗所需要的引數等。建立支付領域模型後,我們路由的解析就變得非常清晰了。路由解析之後,會根據路由型別,觸發不同的動作。比如流程,介面流轉,會交給 UseCase 處理。而特殊流程,比如開啟小程式,開啟 webview, 彈窗這些行為會統一進行處理。我們在第一步把業務流程抽象為 UseCase。第二步則加入了路由機制。加入路由機制後,支付跨平臺的軟體架構演進為這個樣子。加入路由機制後,對比 iOS,安卓原來的舊架構:

  1. 統一了流程,頁面的流轉。清晰,易維護。
  2. 統一了特殊流程的處理,減少重複工作。
  3. 在加入路由機制的時候,結合微信支付和網路密切相關的特點進行了支付領域建模。支付後臺協議重構 2.0 的核心思想也是圍繞著這個路由機制展開。

再來看一下,加入路由機制後,對生產力的提升。以支付流程開啟 WebView, 小程式為例,減少將近 83% 的程式碼。更重要的是,這裡的特殊流程,是在路由機制裡面統一處理的,沒有耦合到業務程式碼中,並且是可複用的。
3. 管理網路請求首先看看原來 iOS 處理支付網路請求的缺陷:原來支付的請求,都是通過一個單例網路中心去發起請求,然後收到回包後,通過拋通知,或者呼叫閉包的方式回撥給業務側。
會存在這樣的問題:

  1. CGI 一對多通訊問題。舉個之前遇到的問題。

    那麼錢包發起的 Cgi 的回包就會覆蓋收付款頁面的資料。之前在 iOS 只能通過修修補補,增加場景值,增加些標記位來解決。可能某一天就會又出現新的坑。
    1. 進入錢包頁面後,發起了一個 Cgi
    2. 然後進入收付款頁面也發起同一個 Cgi.
    3. 如果收付款發起的回包先到
    4. 然後錢包首頁的回包再到。
  2. CGI 生命週期問題。不時會有使用者反饋一下,怎麼沒有做什麼操作,突然就會彈出網路報錯。原因就是 Cgi 的生命週期有問題,在業務結束後,Cgi 的回包仍然得到了處理。

解決方案:將 Cgi 抽象為獨立物件在架構設計上來說,舊架構是通過單例模式實現的集約型 API,而我們新的架構則是通過命令模式實現的離散型 API。也就是將 Cgi 封裝為獨立物件。我們把 Cgi 相關屬性和能力內聚起來。開發業務時,只需簡單繼承 BaseCgi,設定一下引數即可。

  1. 劃分職責,明確生命週期關於 Cgi 由誰發起,之前安卓和 iOS 都沒有一個統一的做法。有些人會放到 Activity,ViewController,和 UI 程式碼耦合起來。因此,在手機遊戲賬號買賣地圖跨平臺軟體架構中,我們統一由業務流程 UseCase 進行發起。並且生命週期是一對一的,一個 Cgi 只會有一個 UseCase 處理, UseCase 銷燬後,Cgi 也隨之銷燬。

對比舊架構:

  1. 杜絕了一對多通訊造成的 Bug
  2. 生命週期和業務邏輯繫結,不會出現業務結束,Cgi 回來後再觸發動作。
  3. 高內聚,低耦合。將 Cgi 相關的資料,能力集中處理,業務側無需感知。
  4. 提供統一的快取,加密能力。

第一步和第二步,我們抽象了業務流程,加入了路由機制。
在第三步管理網路請求後。我們的軟體架構演進為這樣子。

4. 規範資料傳遞

iOS 和安卓的舊架構都存在資訊傳遞不當和資料汙染問題。這個問題最嚴重。iOS 和 安卓都出過不少 bug。首先我們來看看最近現網出現過的問題:之前 iOS 出現,不少內部同事,外部的使用者都在反饋:進行零錢頁後,會無故彈空白框。而支付又和金錢有關,引起使用者的恐慌。具體原因就是:

  1. 進入支付首頁時,後臺返回了資料,然後被寫入到一個公共的 Model.
  2. 然後進入錢包頁,再進入零錢頁。這個公共 model 一路被傳遞過去。
  3. 然後零錢頁讀取了公共 Model 的資料,但是程式碼無法處理,導致出現了這個讓使用者恐慌的問題。

除此之外,之前還有有很多發生在安卓,iOS ,像錢包頁零錢展示錯誤。付款的時候。銀行卡失效等等問題。這些問題五花八門,看起來發生的地方,場景都不一樣。每次遇到這類問題的時候,就只能去修修補補。但是深究下去,會發現真正的原因,是軟體架構上存在的問題:支付舊的架構採用了黑板模式,雖然方便了資料讀寫。但是帶來的問題和收益完全不成正比:

  1. 存在公共讀寫的資料型別。安卓傳遞的資料型別是一個字典,而 iOS 則是一個 Model 物件。所有的介面,業務邏輯都共用一個數據。
  2. 無序的資料流動。資料的流動是不可追溯的,資料的修改可以發生在任意使用公共資料的地方。

那麼支付跨平臺軟體架構,為了杜絕這樣的問題。我是這麼做的:

  1. 去掉公共讀寫的資料型別
  2. 傳遞值型別(Value Type)的資料, 後面流程修改資料時,不影響前面的流程。
  3. 單向傳遞資料,只依賴注入必要資料。
  4. 如果資料修改需要通知前序流程,使用代理模式通訊。

規範資料傳遞後。對比舊架構:

  1. 從架構上根本解決了困擾微信支付已久的資料汙染的問題。
  2. 資料的流動變為單向,資料流動變得可追溯。

前面三步,我們抽象了業務流程,加入了路由機制,統一管理網路請求。那麼規範資料傳遞後,我們軟體架構就演進為這樣子。

總結

軟體的本質複雜性存在於複雜的業務需求中。而軟體架構的本質就是管理複雜性,因此真正的好的架構,正是在複雜的業務需求中反覆提煉和總結歸納而來,解決了真正的業務問題,不是空談。軟體架構除了清理歷史舊架構的缺陷,是我們業務開發的基石之外。還能夠賦能業務,為業務帶來價值。在建立軟體架構的基礎上,還圍繞著軟體架構建立起微信支付的跨平臺自動化資料上報機制,防重複支付,安全橫切等帶來巨大業務收益的能力。有機會的話,後面也會進一步編寫相關文章和大家交流探討。架構是一個不斷演進的過程,隨著新的支付業務基於跨平臺軟體架構的不斷編寫, 我也會對這個架構進行持續的更新迭代。讓這個軟體架構更貼合微信支付,更加健壯和完整。

- End -