餓了麼移動APP的架構演進

發表於2016-03-07

0 引言

時代演進,技術也隨之發展。到今天,APP已然成為絕大多數網際網路企業用來獲取使用者的核心渠道。與此同時,伴隨著業務量的增長,愈來愈大、愈來愈多的APP也在不斷地、持續地挑戰著每一個移動端研發人員的知識深度,而我們的移動端技術人員也在這個不斷接受挑戰的過程中,成就了今天的移動網際網路時代。餓了麼移動APP就是這樣一個挑戰,多使用者量、多業務量,在接受著更多更挑剔使用者的同時,默默地、不斷地演進著移動端的架構。

1 MVC

我們常說,脫離業務談架構就是純粹的刷流氓。餓了麼移動APP的發展也是其業務發展的一面鏡子。

在餓了麼業務發展的早期,移動APP經歷從無到有的階段。為了快速上線搶佔市場,傳統移動APP開發的MVC架構成了“短平快”思路的首選:

圖1 MVC架構

這種架構以層次結構簡單清晰,程式碼容易開發而被大多數人所接受。

在MVC的體系架構中,Controller層負責整個APP中主要邏輯功能的實現;Model層則負責資料結構的描述以及資料持久化的功能;而View層作為展現層負責渲染整個APP的UI。分工清晰,簡潔明瞭;並且這種系統架構在語言框架層就得到了Apple的支援,所以非常適用於APP的startup開發。

然後,這種架構在開發的後期會由於其超高耦和性,從而造就龐大Controller層,而這也是一直被人所詬病。最終的MVC都從Model-View-Controller走向了Massive-View-Controller 的終點。

2 Module Decoupled

“短平快”的MVC架構幫助餓了麼移動APP快速搶佔了市場。而隨著程式碼量的不斷增加,臃腫的Controller層也在漸露頭角;而業務上,餓了麼移動APP也從單一APP發展為多APP齊頭並進的格局。這時候,如果降低耦合,複用已有模組成了架構的第一要務。

架構中,模組複用的第一要求便是程式碼的功能元件化。元件化意味著擁有獨立功能的程式碼從系統中進行抽象並剝離,再以“外掛”的形式插回原有系統中。這樣剝離出來的功能元件,便可以供其他APP進行使用,從而降低系統中模組與模組之間的耦和性;也同時提高了APP之間程式碼的複用性。

餓了麼移動對於元件有兩種定義:公有元件和業務元件。公有元件指的是封裝得比較好的一些SDK,包括一些第三方元件和自己內部使用的元件。如iOS中最著名的網路SDK AFNetworking,Android下OKHttp,都是這類元件的代表。而對於業務元件,則定義為包含了一系列業務功能的整體,例如登入業務元件,註冊業務元件,即為此類元件的典型代表。

對於公有元件,餓了麼移動採取了版本化的管理方式,而這在iOS和Android平臺上也早有比較成熟的解決方案。例如,對於iOS平臺,CocoaPods基本上成為了程式碼元件化管理的標配;在Android平臺上,Gradle也是非常成熟和穩健的方案。採用以上管理工具的另一個原因在於,對企業開發而言,程式碼也是一種商業機密。基於保密性的目的,支援內網搭建私有伺服器成為了必需。以上的管理工具都能夠很好地支援這些操作。

對於業務的元件化,我們採取了業務模組序號產生器制的方式來達到解耦合的目的。每個業務模組對外提供相應的業務介面,同時在系統啟動的時候向Excalibur系統註冊自己模組的Scheme(Excalibur是餓了麼移動用來儲存Scheme與模組之間對映的系統,同時能根據Scheme進行Class反射返回)。 當其他業務模組對該業務模組有依賴時,從Excalibur系統中獲取相關例項,並呼叫相應介面來實現呼叫,從而實現了業務模組之間的解耦目的。

而在業務元件,即業務模組的內部,則可以根據不同開發人員的偏好,來實現不同的程式碼架構。如現在討論得比較火的MVVM, MVP等,都可以在模組內部進行而不影響整體系統架構。

這時候的架構看起來更像是這樣:

圖2 EMC架構

這種E(Excalibur)M(Modules)C(Common)架構以高內聚、低耦合為主要的特點,以面向介面程式設計為出發點,降低了模組與模組之間的聯絡。

該架構的另外一大好處則在於解決了不同系統版本的相容性問題。這裡舉iOS平臺下的WebView作為例子來進行說明。Apple從iOS8系統開始提供了一套更好的Web支援框架——WebKit,但在iOS7系統下卻無法相容,從而導致Crash。使用此類架構,可以在iOS7系統下仍然註冊使用傳統的WebView來渲染網頁,而在iOS8及其以上系統註冊WebKit來作為渲染網頁的核心。即避免了Apple嚴格的稽核機制,又達到了動態載入的目的。

3 Hybrid

移動APP的開發有兩種不同的路線,Native APP和Web APP。這兩種路線的區別類似於PC時代開發應用程式時的C/S架構和 B/S架構。

以上我們談到的都屬於典型的Native APP,即所有的程式都由本地元件渲染完成。這類APP優點是顯而易見的,渲染速度快、使用者體驗好;缺點同時也十分突出:出現了錯誤一定要等待下一次使用者進行APP更新才能夠修復。

Web APP的優點恰好就是Native APP的缺點所在,其頁面全部採用H5撰寫並存放在伺服器端。每次進行頁面渲染時都從伺服器請求最新的頁面。一旦頁面有錯誤伺服器端進行更新便能立刻解決。不過其弊端也容易窺見:每次頁面都需要請求伺服器,造成渲染時等待時間過長,從而導致的使用者體驗不夠完美,並且效能上較Native APP慢了1-2個數量級;與此同時還會導致更多的使用者流量消耗。另一個缺點則在於,Web APP在移動端上呼叫本地的硬體裝置存在一定的不便。不過這些弊端也都有相應的解決方案,如PhoneGap將網頁提前打包在本地以減少網路的請求時間;同時也提供一系列的外掛來訪問本地的硬體裝置。然而,儘管如此,其渲染速度上還是會稍微存在一定的差距。

Hybrid APP則是綜合了二者優缺點的解決方案。餓了麼移動對於此二類APP的觀點在於,純粹展示性的模組會更適合使用Web頁面來達到渲染的目的;而更多的資料操作性、動畫渲染性的模組則更適合採用Native的方式。

基於之前的EMC架構,我們將部分模組重新進行了架構:

圖3 Hybrid-EMC架構

Hybrid-EMC架構中,Web作為一個子模組,註冊加入到整個系統中,從而實現讓業務上需要快速迭代的模組達到實時更新的效果。

4 React-Native & Hot Patch

經過這些年的業務發展,Hybrid提供的展示介面更新方案也逐漸地無法滿足APP更新迭代的需要。因此越來越多的動態部署的方案被提了出來,比如iOS下的JSPatch, waxPatch,Android下的Dexpose,AndFix, ClassLoader,都是比較成熟Hot Patch動態部署解決方案。這些方案的思路都是通過下載遠端伺服器的程式碼來動態更新本地的程式碼行為。

React-Native則屬於另一種動態部署的方案,其核心原理在於通過JavaScript來呼叫本地元件進行介面的渲染。

而餓了麼移動APP發展到今天,各個APP綜合使用者量已經過億。因此一個非常小的Bug所帶來的問題都可能會直接影響到幾萬人的使用。為了保證APP的穩定性和健壯性,Hot Patch方案也就成了當下最有待解決的問題。

根據80%的使用者訪問20%頁面這一80/20原則,保證這20%訪問最頻繁的頁面的穩定性就是保證了80%的APP的穩定性。因此,餓了麼移動對於部分訪問最頻繁的模組進行了React-Native備份。當這部分頁面出現問題時,APP可以通過伺服器的配置,自動切換成React-Native的備份頁面;而與此同時開發人員開發一個小而精的Hot Patch來修復出現的問題。當Hot Patch完成修補後,再切換回Native APP的原生功能。

這時候的架構看起來會像是這樣:

圖4 HotPatch-EMC架構

HotPatch-EMC的架構主要目標在於解決移動APP的穩定性問題。通過RN與Native的主備,可以減少系統APP出錯帶來的失誤成本。

5 結語

我們都知道,對於軟體工程來說,這世上沒有銀彈。對於架構而言其實也非常的適用。業務的不斷更新,帶來了餓了麼移動APP架構的不斷演進。

架構沒有真正的好壞之分,只要適用於自己的業務,就是好的架構!

相關文章