大V店組件化架構(gòu)演進(jìn)實(shí)踐
大V店自2015年創(chuàng)建以來,業(yè)務(wù)快速發(fā)展,項(xiàng)目經(jīng)過多次迭代,目前APP日銷售額已占全平臺百分之70以上,成為公司最重要的入口之一。大V店的用戶端入口,從單一的平臺獨(dú)立App,拓展為大V店、成長盒子、等多個(gè)App入口。大V店所承載的業(yè)務(wù),也從單一的電商,發(fā)展到音頻、課程、視頻播放、視頻直播,等多個(gè)大品類業(yè)務(wù)。業(yè)務(wù)的快速發(fā)展對客戶端架構(gòu)不斷提出新的挑戰(zhàn)。
背景:
三年的時(shí)間里APP經(jīng)過多次迭代,項(xiàng)目經(jīng)手多人,代碼繁復(fù)不堪,代碼復(fù)用性查,已經(jīng)無法滿足現(xiàn)有的業(yè)務(wù)需求和快速迭代開發(fā),公司新一年的產(chǎn)品戰(zhàn)略是在現(xiàn)有平臺的基礎(chǔ)上多元素多面化快速發(fā)展,這就意味著可能會有多個(gè)新的App,多個(gè)業(yè)務(wù)線同時(shí)發(fā)展,現(xiàn)有的代碼架構(gòu)已經(jīng)無法滿足,重構(gòu)迫在眉睫,于是在17年初我們定了年度計(jì)劃,大V店APP組件化。
探索:
一個(gè)好的項(xiàng)目是什么形態(tài),一個(gè)好的架構(gòu)是什么樣子,道理大家都懂無非是高內(nèi)聚低耦合等等,但我認(rèn)為好的架構(gòu)應(yīng)該是跟業(yè)務(wù)形態(tài)高度匹配的,公司業(yè)務(wù)的迭代促使架構(gòu)的演變,而每一次演變都應(yīng)該更貼合業(yè)務(wù)場景,能更好更快更穩(wěn)定的滿足業(yè)務(wù)的迭代,并不是所有的團(tuán)隊(duì)都需要組件化,團(tuán)隊(duì)在不同的時(shí)期受不同業(yè)務(wù)的影響應(yīng)該做出相應(yīng)的變化。
在組件化的路上不可能一帆風(fēng)順,不能一蹴而就,只能循序漸進(jìn),現(xiàn)有的業(yè)務(wù)需要開發(fā)同時(shí)組件化也要同步進(jìn)行,現(xiàn)在的代碼就像一盤散沙,然而要讓一盤散沙變成堡壘絕不是往沙子里加水加泥就能完成的,團(tuán)隊(duì)經(jīng)過反復(fù)討論之后我們決定先進(jìn)行項(xiàng)目分層,項(xiàng)目分為三層基礎(chǔ)層,服務(wù)層,業(yè)務(wù)層,同層級之間可以相互調(diào)用,不同層級之間只可以向下調(diào)用不能相互調(diào)用,在確定了方向之后團(tuán)隊(duì)開始投入有條不紊的重構(gòu)開發(fā)中。
階段一
項(xiàng)目分層之基礎(chǔ)層抽離實(shí)踐:
基礎(chǔ)層即項(xiàng)目的Command層,一個(gè)好的Command層可以快速復(fù)制到多個(gè)項(xiàng)目而不需成本
現(xiàn)有代碼的混亂不好維護(hù)罪魁禍?zhǔn)字痪褪枪ぞ哳惒唤y(tǒng)一,項(xiàng)目組件的濫用和重復(fù)功能的工具類和UI組件到處都是,工具類本身就跟業(yè)務(wù)功能毫無關(guān)系且存在天然的業(yè)務(wù)邊界是最好抽離的部分,所以我們把它發(fā)在了第一步。
抽離時(shí)需要注意的事項(xiàng):
功能重復(fù)或者類邊界不明顯的要直接刪除或者歸到對應(yīng)的工具類中
重構(gòu)要徹底,要保證所有的業(yè)務(wù)代碼使用新的工具類或方法
UI庫的抽離要盡量優(yōu)化解耦,有許多業(yè)務(wù)場景可能會用到同一個(gè)UI組件庫,一個(gè)好的UI庫要有良好的解耦和豐富而自制的接口
HTTP庫的抽離要考慮多工項(xiàng)目使用時(shí)不同的公參和不同的Base(此處我們使用的是暴露攔截器接口給業(yè)務(wù)層實(shí)現(xiàn))

項(xiàng)目分層之服務(wù)層抽離實(shí)踐:
服務(wù)層的定義
服務(wù)層我們定義為DVDServices,即項(xiàng)目中多個(gè)業(yè)務(wù)需要共同使用的服務(wù),比如IM即時(shí)通訊服務(wù),支付服務(wù),AccoutService賬戶服務(wù)等等,服務(wù)層可以調(diào)用基礎(chǔ)層不可以調(diào)用業(yè)務(wù)層,服務(wù)層各個(gè)服務(wù)之間不允許互相調(diào)用
服務(wù)層如何著手
服務(wù)層的抽離可以說是項(xiàng)目中最重要且最繁復(fù)的部分了,現(xiàn)在有的服務(wù)在項(xiàng)目中散落在各處跟業(yè)務(wù)緊緊的耦合在一起,要想拔出來可以說是非常的困難,但也是對技術(shù)能力架構(gòu)能力最有提升的部分,團(tuán)隊(duì)此時(shí)也表現(xiàn)出前所未有的積極,在討論之后我們決定使用模塊負(fù)責(zé)人制度,劃分好各個(gè)服務(wù)之后,分給各個(gè)組員對應(yīng)的服務(wù)模塊,對于這部分模塊負(fù)責(zé)人需要完全重構(gòu),負(fù)責(zé)人在仔細(xì)了解過服務(wù)以及服務(wù)要用到的業(yè)務(wù)之后畫出架構(gòu)圖,召集團(tuán)隊(duì)在一起討論架構(gòu)需要優(yōu)化和不足的地方,最終確定方向之后負(fù)責(zé)人負(fù)責(zé)著手重構(gòu)。
開發(fā)流程
服務(wù)負(fù)責(zé)人整理相關(guān)業(yè)務(wù)抽取出需要整合的服務(wù)
確定要分離的服務(wù)之后著手抽離,整理出技術(shù)方案,架構(gòu)圖
團(tuán)隊(duì)所有人一起過技術(shù)方案,確定出最終方案后評估開發(fā)時(shí)間著手開發(fā)
分離后的服務(wù)要具備良好的可移植性,能在公司項(xiàng)目范圍內(nèi)的APP中隨意使用
分離的服務(wù)理論上屬于獨(dú)立項(xiàng)目,可作為SDK的形式使用
完善WIKI文檔
測試問題
開發(fā)完成后最重要的就是測試上線了,在服務(wù)開發(fā)之前我們會與測試團(tuán)隊(duì)溝通,在確定好開發(fā)提測時(shí)間之后由服務(wù)負(fù)責(zé)人編寫測試case,開發(fā)完成后負(fù)責(zé)人協(xié)同測試人員測試相關(guān)業(yè)務(wù)模塊
技術(shù)架構(gòu)應(yīng)該反映出團(tuán)隊(duì)的組織結(jié)構(gòu),同時(shí),組織結(jié)構(gòu)的變遷,也應(yīng)該導(dǎo)致技術(shù)架構(gòu)的演進(jìn)。
大V店MaMa+平臺下包含學(xué)院和小書庫、智能盒子等等業(yè)務(wù),
對于在我們團(tuán)隊(duì)中已經(jīng)有了的組織結(jié)構(gòu),優(yōu)先組織結(jié)構(gòu),去拆出獨(dú)立的業(yè)務(wù)庫,方便子業(yè)務(wù)庫的同學(xué)內(nèi)部溝通協(xié)作,減少他們跨組織溝通的成本。
同時(shí),我們將負(fù)責(zé)學(xué)院業(yè)務(wù)的大團(tuán)隊(duì),再進(jìn)一步細(xì)化成大V課小組、音頻內(nèi)容小組,由這些小組的同學(xué)去在學(xué)院業(yè)務(wù)下完成更細(xì)維度的學(xué)院子業(yè)務(wù)庫拆分。
根據(jù)組織結(jié)構(gòu)劃分的業(yè)務(wù)庫,天然的存在業(yè)務(wù)邊界,每個(gè)同學(xué)都會按照自己業(yè)務(wù)的目標(biāo)去繼續(xù)完善自己的業(yè)務(wù)庫。
這樣的拆庫對內(nèi)是高內(nèi)聚,對外是低耦合的,有效的降低了內(nèi)外溝通協(xié)作的成本。
AccountService拆分實(shí)踐
最開始并沒有賬戶服務(wù)的概念,只是最簡單的登錄注冊token存儲,不涉及身份變化等等,隨著業(yè)務(wù)的迭代賬戶狀態(tài)逐漸變多需要支持多種身份狀態(tài)隨意切換,以前那種雜亂的寫法使代碼變得難以維護(hù),且基本上無法移植,而平臺基本上各個(gè)端,各個(gè)分支APP都是用的一套賬戶體系,所以我們決定優(yōu)先把賬戶服務(wù)拆分出來,得益于基礎(chǔ)層重構(gòu)的經(jīng)驗(yàn)和擬定好的開發(fā)步驟,賬戶服務(wù)有條不紊的開始了重構(gòu),由相關(guān)負(fù)責(zé)人開始擬定歸納現(xiàn)有的賬戶體系以及用戶場景,畫出架構(gòu)圖再由大家討論是否合理,賬戶體系抽離的需要控制好那些接口需要開放,那些內(nèi)容需要存儲,數(shù)據(jù)改變的時(shí)機(jī)以及刷新機(jī)制和通知機(jī)制,緩存機(jī)制等等針對這些種種我們歸納出了以下幾點(diǎn):
- 業(yè)務(wù)層通過賬戶服務(wù)無非是需要獲取信息和收到通知信息
在跟后端同學(xué)溝通之后我們把大部分零散的接口合并,組合成一個(gè)專門獲取個(gè)人信息的接口,數(shù)據(jù)發(fā)生改變之后發(fā)送通知,接受到通知的業(yè)務(wù)即做出相應(yīng)的改變
- 服務(wù)層要做的事,提供set和get個(gè)人信息的相關(guān)方法,個(gè)人信息的緩存,賬戶生命周期的管理,以及各種賬戶狀態(tài)改變之后需要調(diào)用的后端接口
這一塊的設(shè)計(jì)總結(jié)一下就是面向?qū)ο蟮姆庋b思想業(yè)務(wù)不需要知道內(nèi)部實(shí)現(xiàn)邏輯,只需要調(diào)用相關(guān)的API剩下的就交由服務(wù)自己去完成
AccountService工作示意圖:

AccountService的拆分極大的方便了各業(yè)務(wù)層的調(diào)用,和維護(hù)成本,數(shù)據(jù)的統(tǒng)一處理更好的保證了各業(yè)務(wù)數(shù)據(jù)的統(tǒng)一性
對于賬戶服務(wù)的拆分總結(jié)了以下幾點(diǎn):
- 服務(wù)的設(shè)計(jì)不要拘泥于形式:不要拘泥于某種架構(gòu)方式不可,能貼合業(yè)務(wù)場景的架構(gòu)才是好架構(gòu)
- 要掌握好服務(wù)自身收放的度:過多的對外借款意味著維護(hù)成本的增加,過少的話又不能滿足業(yè)務(wù)場景,這個(gè)度需要掌握好,對于沒必要開放的接口絕不開放
最終拆分出的服務(wù)

抽離了基礎(chǔ)層和服務(wù)層,項(xiàng)目的解耦性和可擴(kuò)展性大大的提升,不過這只是我們組件化的第一步,此時(shí)我們的服務(wù)層基礎(chǔ)層尚還處在一個(gè)工程之中,服務(wù)層和基礎(chǔ)層以module的形式依賴,下一步就是徹底的抽離出工程
階段二
組件化實(shí)踐:
服務(wù)層和基礎(chǔ)層的抽離讓組件化的關(guān)鍵部分變得了容易很多
架構(gòu)的目的是用于管理復(fù)雜性、易變性和不確定性,以確保在長期的系統(tǒng)演化過程中,一部分架構(gòu)的變化不會對架構(gòu)的其它部分產(chǎn)生不必要的負(fù)面影響。這樣做可以確保業(yè)務(wù)和研發(fā)效率的敏捷,讓應(yīng)用的易變部分能夠頻繁地變化,對應(yīng)用的其它部分的影響盡可能的小。
-
服務(wù)的的抽離和管理
至此服務(wù)層和基礎(chǔ)層我們已經(jīng)抽離解耦,但是代碼依然還在我們的工程中,使用還是需要花費(fèi)不少成本,而且管理起來也不方便,所以這里我們選擇了把組件上傳到maven倉庫(關(guān)于maven倉庫的搭建大家自行搜索,下面會貼出一篇教學(xué)),在運(yùn)維同學(xué)的幫助下我們搭建了基于nexus的android包管理私服,然后分別創(chuàng)建common層和services的gitlab倉庫,把各個(gè)組件分別上傳,就可以在需要使用的地方通過gradle引用,大大的提高了可維護(hù)性和使用效率,當(dāng)更新完組件的時(shí)候各個(gè)業(yè)務(wù)模塊只需要修改一下版本號即可。

當(dāng)然各個(gè)組件之間有可能互相引用,尤其是common層的util組件,不過沒關(guān)系在主工程中我們引用的時(shí)候使用exclude取除重復(fù)的依賴即可,Exclude可以設(shè)置不編譯指定的模塊,這樣即可避免重復(fù)依賴問題
maven倉庫搭建教程:
https://blog.csdn.net/ouyang_peng/article/details/56872556
-
路由的選擇&為什么要選擇路由
在比較了目前的主流框架之后,選擇了ARouter,關(guān)于框架的使用和原理想必也不必多說,網(wǎng)上有很多不錯(cuò)的教學(xué),為什么需要路由,這里我們直接引用一段關(guān)于Arouter博客的分析,里面已經(jīng)說的很全面了:
顯示Intent:項(xiàng)目龐大以后,類依賴耦合太大,不適合組件化拆分
隱式Intent:協(xié)作困難,調(diào)用時(shí)候不知道調(diào)什么參數(shù)
每個(gè)注冊了Scheme的Activity都可以直接打開,有安全風(fēng)險(xiǎn)
AndroidMainfest集中式管理比較臃腫
無法動(dòng)態(tài)修改路由,如果頁面出錯(cuò),無法動(dòng)態(tài)降級
無法動(dòng)態(tài)攔截跳轉(zhuǎn),譬如未登錄的情況下,打開登錄頁面,登錄成功后接著打開剛才想打開的頁面
-
H5、Android、iOS地址不一樣,不利于統(tǒng)一跳轉(zhuǎn)
博客地址:http://www.jcodecraeer.com/a/anzhuokaifa/2019/0220/12655.html
其中關(guān)于公共組件數(shù)據(jù)交換的問題,在服務(wù)層我們已經(jīng)解決了,被多個(gè)業(yè)務(wù)引用的功能我們已經(jīng)抽離到下層,并且使用gradle管理
-
業(yè)務(wù)組件的拆分實(shí)踐
業(yè)務(wù)層是整個(gè)組件化過程中最復(fù)雜繁復(fù)最需要耐心和配合的工作,越大越久的項(xiàng)目耦合越深,拆分起來越痛苦,也越容易出問題,總結(jié)以下兩點(diǎn):
分工問題:有些年代久遠(yuǎn)的業(yè)務(wù),代碼邏輯可能很久不動(dòng),甚至可能相關(guān)的開發(fā)者也已經(jīng)離職,這類業(yè)務(wù)是最難拆分和最容易出錯(cuò)的,因此在拆分初期我們跟服務(wù)層一樣首先為每位同學(xué)分配了業(yè)務(wù)方向,自己拆分相關(guān)的業(yè)務(wù)組件,在遇到和別的組件有耦合時(shí)可以直接找到相關(guān)負(fù)責(zé)人討論技術(shù)方案,這樣的好處是大家能最快的熟悉自己相關(guān)的業(yè)務(wù)代碼,而不是面對一整個(gè)工程,當(dāng)模塊出現(xiàn)問題的時(shí)候也能第一時(shí)間找到負(fù)責(zé)人溝通解決
測試問題:由相關(guān)組件負(fù)責(zé)人聯(lián)系測試妹子,商討測試時(shí)間共同編寫測試用例,在確定大概時(shí)間之后,上報(bào)相關(guān)開發(fā)節(jié)點(diǎn)和進(jìn)度時(shí)間表
最終架構(gòu)示意圖:

結(jié)語
也許我們的組件化跟市面上的有所不同,但還是那句話:
“技術(shù)架構(gòu)應(yīng)該反映出團(tuán)隊(duì)的組織結(jié)構(gòu),同時(shí),組織結(jié)構(gòu)的變遷,也應(yīng)該導(dǎo)致技術(shù)架構(gòu)的演進(jìn)?!?,你的架構(gòu)應(yīng)該是最適合你當(dāng)前團(tuán)隊(duì)以及公司業(yè)務(wù)線的,能與業(yè)務(wù)和團(tuán)隊(duì)高效配合的,好的架構(gòu)并不是依照某個(gè)模板刻畫而成,而是會根據(jù)團(tuán)隊(duì)和業(yè)務(wù)成長而成長。