背景
在早期app功能比較簡單業(yè)務不復雜的情況下,我們一般都不需要組件化,最多就是基礎庫抽成私有庫,正常情況下不需要劃分業(yè)務組件也不需要路由等。在合適的時機引入合適的框架,而不是盲目的跟風炫技。特別是有些app就1-2個人更新迭代,業(yè)務也相對比較簡單,如果硬要引入組件化無疑是加劇迭代成本。所以組件化我認為他應該是中大型項目為了更好的業(yè)務解耦以及布局平臺化、生態(tài)化的必要手段之一。
我們的app是一款saas B端app,集成了新零售、微商城、餐廳、智慧美業(yè)、客來店等十幾個業(yè)務線的B端app集合.理論上每個解決方案都是要有一個獨立自己的商家app,方便商家處理訂單,管理會員等.但是如果獨立app會有以下幾個問題.
1.商家如果購買了多個解決方案,需要下載多個app
2.商家如果從一個解決方案升級到另外一個解決方案,需要重新下載app
3.一個業(yè)務線想把其中某些功能當作插件給其他解決方案使用就比較麻煩.比如一家連鎖火鍋店,他應該購買我們餐廳解決方案,但是他同時又有火鍋底料需要在微商城售賣.這種業(yè)務場景,你多個app是很難處理的.
都是給商家用的,處理訂單,管理會員等,雖然業(yè)務場景不一樣,但是有些東西還是可以抽象共用,節(jié)約研發(fā)成本. 這就是后面要說的app中臺.
基于以上幾點原因,我們就需要把不同業(yè)務功能合并到一個app上,并且需要保證每個業(yè)務的獨立迭代、互不影響,每條業(yè)務線的功能都可以獨立運行調試發(fā)布,必要的時候也可以拆分成獨立app,保持子app和主app并行迭代。類似天貓和淘寶、外賣和美團還有主app上的其他業(yè)務,都是一個個獨立的團隊在迭代。而我們雖然是B端app,但是用到的技術卻是一樣的,區(qū)別應該就在于業(yè)務組件的拆分和基礎組件的封裝以及業(yè)務場景組裝等。
組件化

如圖一個app不同功能模塊后端已經進行了微服務了,但是前臺app還沒有進行組件化,還是單過程app,如果業(yè)務不是很復雜其實如上圖構架圖每個模塊用文件夾區(qū)分也是可以的。但是如果業(yè)務已經足夠復雜,并且每個迭代按模塊劃分的比較清楚的情況下,還是建議采用組件化。這個app是組件化的,且每個業(yè)務內部同樣也可以進行二次拆分子組件。

先來看看我們app的全景圖,如圖可以很清楚的看出這是一個多業(yè)務的聚合型app,每個業(yè)務他們的框架還可以不一樣,具體后面展開說明下。先說下入口工程,也就是app的主工程又稱殼工程,主工程不要有業(yè)務代碼,連基礎庫工具類也不要有,只放一些icon、啟動頁等和app強相關的基本配置。檢查一個app是否組件化徹底,第一步就是看他的主工程是否有代碼。
為什么主工程不能用代碼?
有些開發(fā)者只把相對獨立的模塊拆分出業(yè)務組件,一些不知道如何拆分的或組裝業(yè)務組件的代碼會放到主工程。這種做法是剛開始做組件化或老工程改造很容易采用的方法,但是具體去看他們的代碼就會發(fā)現,每個業(yè)務組件雖然功能獨立但是拆的很輕不徹底,主工程還是會不可避免的臃腫,特別是隨著業(yè)務越來越復雜,好像需要組裝和跨組件的業(yè)務處理也越來越多了。不自覺的研發(fā)可能還會把一些本來應該是業(yè)務組件處理的邏輯,為了寫起來更加方便就直接寫到主工程了。這樣的組件化能算徹底嗎?所以要做到徹底組件化,主工程不要有任何代碼,切斷研發(fā)對主工程的依賴,每個業(yè)務組件都是平等的,也不能相互依賴。
主工程沒有任何代碼還有一個好處,那就是后面如果需要做平臺化或子業(yè)務需要有自己獨立app,只要選擇合適的業(yè)務組件給一個殼工程配置上icon等相關配置一個app就完成了,不需要擔心主工程里面的代碼如何改造剝離等問題。
還有組件化很重要的一個原則,必須保證每個組件向下單項依賴,不要出現相互依賴或反向依賴的情況。由于很多開發(fā)者做組件化的時候是用pod來管理自己的組件,由于pod依賴進來私有庫后是沒有層次限制的,都可以進行import調用,這樣如果開發(fā)者在框架層面沒有規(guī)定限制好,具體業(yè)務開發(fā)就很難避免相互調用導致依賴混亂。
從構架圖上看每層都只能向下依賴,不能向上依賴或左右依賴,也就是主工程依賴業(yè)務組件,業(yè)務組件依賴基礎庫,基礎庫依賴私有庫和第三方庫,業(yè)務組件之間不能相互依賴。由于我們的app比較大型不像其他電商類app一個業(yè)務組件就是一個模塊,這邊圖上的業(yè)務組件都是一個個業(yè)務線,也就是說他們都可以是一個個獨立的app,所以每個業(yè)務組件都很復雜,所以這里指的業(yè)務組件可能是多個業(yè)務組件和基礎組件組成的聚合組件。
下面我們來簡單展開幾個“業(yè)務組件(聚合組件)”,來對整個app框架有更深層次的了解。


隨便找了2個業(yè)務組件展開,可以很明顯的看到每個業(yè)務線的架構都不一樣,每個業(yè)務可以根據自己的業(yè)務特性選擇不一樣的框架,進行不一樣的業(yè)務劃分很組裝方式。但是必須都有統(tǒng)一的interface層用于該業(yè)務組件和其他業(yè)務通信,就好比每個業(yè)務組件的鏈接頭,插槽和插頭必須安裝一樣的規(guī)范,不能一個USB的插頭去對接一個TPC的插槽。
問題
把原來應該是十幾個app的功能合并成一個app,框架應該如何設計才能讓這十幾個業(yè)務迭代互不影響.我們會遇到哪些問題呢?
1.如何避免業(yè)務間迭代不會相互牽制?多方捆綁形成兩人三足的局面呢?
按照傳統(tǒng)早期的app框架,一般一個app就一個代碼倉庫單一工程,選擇MVC或MVVM、MVP進行分層,然后再抽一些通用工具類等.大一點的公司一般還會搭建自己的私服(cocoapods和maven)來沉淀公司的各種工具類. 一般的小項目、小團隊采用這種方式沒啥問題,招一個ios一個android就能夠先跑起來. 但是我們是要把十幾個app合并成一個app,如果采用這樣的框架,無法避免的就是所有的業(yè)務方都相互捆綁在一起了,每次迭代發(fā)布都需要等所有人ready,需要所有人一起配合,一個倒下了所有人玩完;所以也就必然造成發(fā)布周期長、過程痛苦。
?單一工程的開發(fā)模式,客戶端承載業(yè)務越來越多,業(yè)務之間的依賴越來越多,系統(tǒng)耦合嚴重, 擴展困難,維護變得異常復雜
?業(yè)務更新:端側的發(fā)版特性,限制了業(yè)務更新的靈活性
?協(xié)同成本高,牽一發(fā)而動全身,不敏捷
?APP發(fā)版周期長,業(yè)務創(chuàng)新慢
?各自業(yè)務發(fā)展依賴版本,無法獨立迭代
如上圖是一個單一工程同一個代碼倉庫的普通項目,它通過統(tǒng)一的網關對接3個后端團隊,也就是三條獨立業(yè)務.按理說這3條業(yè)務應該互相不影響,他們的迭代更是不應該出現互相牽制的情況.但是由于app是一個,可能都需要改同一個模塊,很容易出現代碼沖突迭代相互牽制.所以我們不能采用傳統(tǒng)框架,我們需要改造.第一步按業(yè)務拆分app,每個業(yè)務都有一個獨立的代碼倉庫,獨立的工程,讓他們之間的迭代互不影響.新框架優(yōu)勢:
1.每個業(yè)務線都是單獨的一個項目,有自己的代碼庫管理,有自己的代碼版本。
2.幾個業(yè)務線可以并行開發(fā),先開發(fā)好的可以先提測,服務可以先上線。
3.如果某個服務延期了,我們可以把那條業(yè)務線的代碼切換到上個版本的代碼,打包先發(fā)布上線。
4.后期如果需要拋棄一個業(yè)務線的話我們也可以很快的把相關代碼和資源去掉。
5.如果需要某2個業(yè)務線打包成一個新的app,我們只需要搞個新的主工程,做些配置和小調整就可以搞個新app出來。
每個業(yè)務都是一個獨立的代碼倉庫一個獨立的工程,只要給每個項目配上一個空的主工程它就是一個獨立的app,可以獨立開發(fā)、調試、提測,只是在對外發(fā)布的時候用一個主工程包含全部業(yè)務打包發(fā)布,就實現了我們需要把十幾個app合并成一個app,而且迭代互不影響的目標.
涉及到的一些技術點:
1.每個業(yè)務組件都是一個個庫項目,它所對應的資源都應該歸屬它自己
2.全局變量解耦設計,拆分出基礎的下沉,業(yè)務的回歸業(yè)務
3.賬號體系、權限、設置等所有業(yè)務線都需要引用和設置. 這塊是拆分出賬號組件,公司賬號體系只要是打通的,他們基本通用.
2.版本更新提示如何設計?
每個app都會經??吹揭恍椏?提示用戶升級新版本,有些甚至不升級就不讓用app.這就導致了用戶不得不升級,而有時候升級了卻發(fā)現功能根本沒有變化.這很可能就是他升級的功能你沒有權限或根本沒用到.如何精準提示用戶升級,以及如何灰度提示都需要好好設計.
關于這塊設計我們是這樣做的,每個業(yè)務組件都有一個唯一的組件標識,在組件創(chuàng)建的時候定義一個唯一的組件標識,并注冊到DevOps平臺上,這樣我們就可以控制對每個業(yè)務組件單獨配置升級提升,也只有當app進入到該業(yè)務組件的時候才會觸發(fā)。還有很重要的一點,不管是哪個業(yè)務組件觸發(fā)了升級提升,點擊升級的時候必須直接升級到最新版本。這樣就不會出現進入一個組件升級到1.1,再點擊另外一個組件又提升升級到1.3等這種情況了。配置還是可以更新組件更新情況來配置,但是用戶體驗上卻不會出現多次升級的情況。
其實引入組件標識的概念除了做版本升級設計之外還有很多其他的用處,比如虛擬路由管理等都需要用到。下面稍微擴展下組件標識和組件以及業(yè)務的關系。如下圖多個組件庫可以對應同一個組件標識,但是不能一個組件庫有多個組件標識,這樣查詢的時候會戳亂,就好比一個人只能有一個名字,但是可以很多個人都叫張三一樣。如果組件標識是按業(yè)務為度拆分定義的正常情況下一個組件標識對應一個業(yè)務線,但是偶爾有一些奇葩的需求,就是這2個業(yè)務功能基本差不多,只是換個名字換個概念包裝下變成一個新的業(yè)務售賣的時候,只有少數功能做了隱藏,那不可能復制2份一樣的代碼,迭代開發(fā)2次,這時候就會出現多個業(yè)務線對應同一個組件標識的情況。

3.發(fā)版如何操作?
火車式發(fā)布應該是行業(yè)比較通用的一種發(fā)布方式,一周一個版本或2周一個版本,趕的上這趟火車的項目就這次一起發(fā)布,趕不上的等下一班車.但是這塊也是有一些規(guī)范來約束不要就會很亂,導致漏發(fā)錯發(fā)等問題.
火車式發(fā)布和版本制最大的一個區(qū)別就是是否提前圈定版本上線內容,而不是有固定的發(fā)版時間就能算火車式發(fā)布了.我之前和一個面試的小伙伴聊起這個火車式發(fā)布模式,他們公司也基本是2周一個版本,看上去也是火車式發(fā)布模式,但是細聊下來我發(fā)現他們可能并不是嚴格意義上的火車式發(fā)布模式,比如他們會在這次發(fā)布上線后開會確認下次發(fā)布版本包含哪些內容.然后在圈定為下個版本發(fā)布的項目就按這個時間點來趕.這樣只要其中一個項目出現問題,可能就會影響到這個版本發(fā)布,雖然如果評估趕不上這趟發(fā)布最后也是可以拿掉這個項目在這次迭代里面.但是這樣給人的感覺其實就不一樣了,火車式發(fā)布應該是在發(fā)車前多久某個時間(停止檢票之前),我可以隨時買票和退票,而不是像那種上個版本上線之后就需要確認下一班車是否買票.同樣我開車之前,這個開車人員(打包人員)其實也不需要去清點買票的人是否都上車了(雖然我們現在會做一個簡單的清點),是否來得及上車,上不上車應該是這個項目自己決定.
今天的組件化沒有講路由也沒有講組件化的細節(jié)和具體技術點,因為網上類似的文章已經有很多了,沒必要重復來說,只要選擇自己合適的技術應用就可以,今天主要講的是大框架上如何拆分如何設計。關于B端app的組件化這是之前很早寫的一篇關于組件化細節(jié)的文章有興趣的可以看一看,里面關于Assets.xcassets合并問題已經突破處理了,只是沒更新。