手機天貓解耦之路
本文為作者在GMTC全球移動技術(shù)大會上的演講整理成文。演講PPT見:http://ppt.geekbang.org/slide/show/194
本文標題是解耦,聊解耦可以有很多方法,本文以架構(gòu)進化為線索給各位分享手機天貓的解耦之路。我想,在手機天貓的成長過程中,一些形而上的思考和沉淀固然是對大家有參考價值的,而工具和方案則借鑒價值更大。所以本文會較少篇幅放在講過程和原因,比較多篇幅放在講工具和方案。
什么在推動進化
作為技術(shù)團隊,我們升級技術(shù)架構(gòu)有各種原因,而什么什么因素是最關(guān)鍵的,什么可以成為進化理由?
- 業(yè)務(wù)升級
最重要的因素一定是業(yè)務(wù)升級。一個不能產(chǎn)生業(yè)務(wù)價值的技術(shù)是不值得關(guān)注的,更不值得去實施。
好技術(shù)永遠要比業(yè)務(wù)先走一步,在前邊不遠的地方等著業(yè)務(wù)追上來——技術(shù)驅(qū)動業(yè)務(wù)。所以業(yè)務(wù)不斷升級,就要求我們的技術(shù)架構(gòu)要跑的更快。
- 團隊規(guī)模 & 合作模式
另一個推動架構(gòu)進化的重要因素就是團隊規(guī)模。技術(shù)架構(gòu)最重要的作用之一是保障生產(chǎn)效率和生產(chǎn)質(zhì)量。那么人的因素就非常關(guān)鍵。人多了,合作復雜了,技術(shù)架構(gòu)就必須升級,去保障這么多人在一起工作的時候,效率高,不出問題。
- 代碼規(guī)模 & 工程規(guī)模
代碼和工程的規(guī)模是一個自然發(fā)展的結(jié)果,業(yè)務(wù)復雜了,團隊大了,人多了,代碼規(guī)模和工程規(guī)模必然上升。一個數(shù)十萬DAU的App和一個千萬DAU的App,規(guī)模上的差距雖不是必然,卻也是極大概率了。
一個人,幾十個文件,完成一個簡單功能的產(chǎn)品,和十幾個人,數(shù)千個文件,功能復雜的產(chǎn)品,進一步到十幾個團隊,數(shù)百個模塊,一個平臺及產(chǎn)品的技術(shù)架構(gòu)必然千差萬別。
- 新技術(shù)
新技術(shù),就是工程師自己的事了。業(yè)界總會有新的技術(shù)出來,這個技術(shù)可能是別人家工程師,為了適應(yīng)他們的業(yè)務(wù)發(fā)展和架構(gòu)演化而研發(fā)出來的。但是技術(shù)沒有邊界,新技術(shù)好,能給我們帶來價值,提升我們的效率,那我們就拿來用。
舉個例子:Facebook的RN出來,我們都覺得不錯,應(yīng)該也有很多公司確實大規(guī)模的使用了。大規(guī)模的使用RN做開發(fā),也就會對你原本的架構(gòu)提出升級的要求。
當然更重要的是如何去平衡快速升級技術(shù)架構(gòu)的好奇心和恰好滿足業(yè)務(wù)與團隊要求這兩件事。過度追求技術(shù)架構(gòu)革新,過度追求新技術(shù),不但不能給業(yè)務(wù)和團隊帶來推動作用,反而會造成災(zāi)難。所以,作為一個優(yōu)秀的技術(shù)團隊永遠要權(quán)衡做或不做,多做或少做。
架構(gòu)怎么進化
架構(gòu)進化體現(xiàn)在哪些方面,作為一個技術(shù)團隊我們要如何把架構(gòu)進化落地?這個問題因項目而異,因團隊而異,因方向而異。本文只介紹手機天貓在發(fā)展過程中,與解耦相關(guān)的進化歷程。
- 升級開發(fā)模式
開發(fā)模式的概念有點大,本文就只討論和解耦這件事相關(guān)的:團隊合作方式和工程組織形式。下文單獨一節(jié)聊這個事,此處不贅述。
- 各維度解耦
工程大了以后,要分拆,不管是組件化還是插件化,還是什么,解耦是***步,而且是各個維度的解耦。
- 完善工具集
模式演進的過程中,解耦的過程中,就會衍生出很多的工具。在進化過程里我們也會去思考,哪些工作是需要工具化的,主動去開發(fā)工具。一個完善的工具集,會極大提升團隊的生產(chǎn)力,可以說是最有價值的部分。
開發(fā)模式升級
手機天貓團隊從一個三端不到十個人的小團隊,成長到現(xiàn)在一個接近兩百人的大團隊,后文詳細描述開發(fā)模式經(jīng)歷了怎么樣的變更?
- 一個工程
三年前,手機天貓團隊剛剛組建,十個左右工程師,開發(fā)***版只具備基礎(chǔ)功能的天貓App。整個團隊就這么幾號人,包括iOS,Android和Server三端,一個平臺上也就三四個人;App的功能也非常簡單,能完成基本的導購和交易流程。
天貓App就使用了最簡單的架構(gòu),獨立工程,MVC架構(gòu)。而且我們判斷在這種情況下這樣的架構(gòu)是完全夠用的,事實如此。
- 模塊化
隨著無線業(yè)務(wù)的發(fā)展,手機天貓的團隊開始爆炸式的擴張。很快一個團隊變兩個,兩個變四個。隨著團隊增加,出現(xiàn)團隊分工,工程也越來越大,我們開始發(fā)現(xiàn)原始的架構(gòu)已經(jīng)開始不夠用,拆分模塊勢在必行。
在這個階段,手機天貓的模塊拆分也做得非常簡陋。先按功能把工程做橫向分層,在業(yè)務(wù)層再做縱向梳理。把不同的模塊代碼簡單的放在一個文件夾里,而工程的組織形式并沒有發(fā)生變化。
如此拆分,我們做到代碼獨立,跨團隊基本不會在同一個模塊代碼上產(chǎn)生沖突。
- 插件化
進一步發(fā)展,業(yè)務(wù)越來越復雜,團隊工作越發(fā)細分,人也越來越多,代碼量越來越大。簡單的使用文件夾來組織模塊的方式顯得力不從心。多業(yè)務(wù)跨團隊,不同的開發(fā)節(jié)奏,復雜的依賴關(guān)系,導致我們會花掉大量的時間解決編譯不過的問題。等待其他模塊集成這件事居然成了我們開發(fā)效率***的瓶頸。
如何解決這個問題,我們的方案是插件化。那么插件和模塊有什么區(qū)別?我認為二者***的區(qū)別在于獨立性。插件是可以獨立開發(fā),獨立發(fā)布,獨立運行的,而模塊則必須依賴主工程的環(huán)境。具備獨立性的插件可以很好的隔離跨團隊之間的依賴,彼此獨立開發(fā),按照各自的節(jié)奏發(fā)布版本。
基于這樣的思考,我們引入依賴管理設(shè)施(iOS引入了Cocoa Pods,Android使用Maven);把此前的模塊進一步剝離成獨立工程,單獨做版本管理;每個獨立的插件對發(fā)布的版本號負責,不論是其他插件還是主工程都依賴插件發(fā)布的穩(wěn)定版本。
然而,但是,But,插件化這件事并沒有我們想象的那么美好。代碼出來了,但是不能獨立編譯,依賴管理設(shè)施有了,但是管不好。由于我們此前從未梳理過依賴關(guān)系,所以不管是模塊還是插件,只是一種代碼管理和發(fā)布流程的工作法,解決不了獨立開發(fā)和獨立運行的問題。在這個階段,我們選擇了容忍這個問題,因為獨立開發(fā)和獨立運行這兩件事對我們來說似乎并不是那么的有價值,而無法實現(xiàn)這兩件事也并不成為我們的瓶頸。所以大家還是在一個工程里,只是代碼提交到不同的倉庫,然后通過依賴管理設(shè)施,通過版本號拼裝成主工程,源代碼最終運行還是揉在一起。
- 獨立發(fā)布
無法獨立發(fā)布會帶來什么問題?非常明顯,慢!插件化一段時間后,我們發(fā)現(xiàn)慢的問題嚴重影響著我們的效率。在這個階段,我們已經(jīng)有超過十個團隊,iOS工程的源碼文件超過一萬個。由于主工程是通過各插件的源碼組合起來的,每一次重新索引和編譯,都要消耗超過半個小時的時間。
要解決這個問題,就是要把插件化進行到底,實現(xiàn)插件的另外兩個獨立——獨立開發(fā)和獨立運行。最重要的工作就是我們今天的主題解耦,梳理各個插件之間的依賴關(guān)系。讓每一個獨立插件盡可能少的依賴其他插件,在最小范圍內(nèi)正常編譯執(zhí)行。每次發(fā)布不再是一個穩(wěn)定版本號,而是一個穩(wěn)定的二進制包。
如此依賴,我們把超過半小時的編譯過程拆分到數(shù)十個模塊中,而主工程依賴數(shù)十個二進制包,編譯也就快了。
整個模式升級基本上經(jīng)歷了這樣幾個階段:
- 代碼獨立,先從形式上解耦
- 獨立代碼工程化,為獨立運行打下基礎(chǔ)
- 梳理依賴關(guān)系,獨立工程可編譯
- 放棄源碼依賴,提速集成編譯
一路走來,一步一個腳印,最終實現(xiàn)完整的解耦。在這個過程中我們沉淀了不少的方法論和***實踐,我想有兩個工具是值得介紹的,下文詳述。
解耦工具箱
工欲善其事,必先利其器。這句話每個人都在說,卻不是每個人都能做到。一個具有工具文化的團隊會在質(zhì)量,效率各個方面都會有很大優(yōu)勢。
一個工程,從原始狀態(tài)迅速膨脹到天貓現(xiàn)在的體量的,依賴關(guān)系之復雜,超乎想象。
在這個膨脹過程里,我把耦合分成三類:
- 界面耦合,就是用戶操作流程里,從首頁-到搜索-到詳情-再進店,這些界面的跳轉(zhuǎn)是硬編碼的
- 依賴耦合,顧名思義,兩個模塊之間的有依賴,就是耦合
- 工程耦合,每個模塊有自己的生命周期和運行時,每個模塊在生產(chǎn)環(huán)境里又需要依賴主工程的運行時
Beehive(Beehive已經(jīng)開源,可以在Github上看到源碼:https://github.com/alibaba/BeeHive)
Beehive是一個運行時框架,主要解決依賴耦合和工程耦合。
說到耦合,體量如手機天貓這樣的一個App,各種依賴關(guān)系必然非常復雜,模塊與模塊的耦合也必然千絲萬縷。我們要做的并不是把這些依賴和耦合一一處理掉,而是進行梳理,把不合理的找出來,解決掉,讓整個工程處在一個健康合理的依賴和耦合范圍內(nèi)。有問題的依賴基本有這樣幾種:
- 模塊循環(huán)依賴
- 層間反向依賴
- 非強功能依賴
下圖是一張依賴的示意圖。
幾條虛線的依賴關(guān)系是我認為有問題的依賴,而抽象出有問題的幾個模塊
引入Beehive后,依賴關(guān)系會把幾條紅線全部引向Beehive模塊,而Beehive模塊則是獨立于各層之外的。
Beehive的原理是,每一個對外提供服務(wù)的模塊,需要注冊一個抽象接口到Beehive提供的Interfaces(接口池)。注意,在這個池子里只有抽象接口。
開發(fā)階段,調(diào)用方依賴接口池中響應(yīng)的接口,并以接口為參數(shù),通過Beehive提供的工廠方法獲取一個服務(wù)實例,這個實例可以正常進行服務(wù)。
運行時階段,Beehive工廠方法根據(jù)服務(wù)的注冊配置,構(gòu)造服務(wù)實例。若:當前的運行環(huán)境沒有依賴提供服務(wù)的模塊,則返回空;若:當前運行環(huán)境依賴關(guān)系完整,則開始構(gòu)造服務(wù),并返回。
通過這樣的方案,就可以實現(xiàn)模塊間解耦。
統(tǒng)跳協(xié)議 & Rewrite引擎
統(tǒng)調(diào)協(xié)議是一個基于URL的跳轉(zhuǎn)方案,配合Rewrite引擎實現(xiàn)全App調(diào)用解耦。此前蘋果核有一篇文章詳細介紹,這里我就不詳述細節(jié):
http://pingguohe.net/2015/11/24/Navigator-and-Rewrite.html
Beehive和統(tǒng)跳&Rewrite的區(qū)別
Beehive和統(tǒng)跳協(xié)議的目的都是解耦,然后二者所關(guān)注的重心不同。統(tǒng)跳主要為界面解耦服務(wù),業(yè)務(wù)要求界面鏈路的強動態(tài)性;Beehive則為模塊解耦,解決模塊強依賴帶來的開發(fā)階段痛苦。
以上,就是我們在過去的幾年里,整個手機天貓所經(jīng)歷的解耦過程。在這個過程里,我們有過很多思考,也踩了很多坑,當然也沉淀了很多好用的工具。希望接下來能有更多機會跟各位分享,也歡迎各位跟我們交流,互相學習。
手機天貓其它文章推薦:
不要寫死!天貓App的動態(tài)化配置中心實踐
天貓App A/B測試實踐
安全模式:天貓App啟動保護實踐