轉(zhuǎn)載自Casa Taloyum架構(gòu)系列文章
iOS應(yīng)用架構(gòu)談 開篇
iOS應(yīng)用架構(gòu)談 view層的組織和調(diào)用方案
iOS應(yīng)用架構(gòu)談 網(wǎng)絡(luò)層設(shè)計(jì)方案
iOS應(yīng)用架構(gòu)談 本地持久化方案及動(dòng)態(tài)部署
iOS應(yīng)用架構(gòu)談 組件化方案
緣由
之前安居客iOS app的第二版架構(gòu)大部分內(nèi)容是我做的升薯,期間有總結(jié)了一些經(jīng)驗(yàn)。在將近一年之后击困,前同事zzz在微信朋友圈上發(fā)了一個(gè)問題:假如問你一個(gè)iOS or Android app的架構(gòu)涎劈,你會(huì)從哪些方面來說呢?
當(dāng)時(shí)看到這個(gè)問題正好在乘公車回家的路上沛励,閑來無聊就答了一把责语。在zzz在微信朋友圈上追問了幾個(gè)問題之后,我覺得有必要開個(gè)博客專門來講講一些個(gè)人見解目派。
其實(shí)對(duì)于iOS客戶端應(yīng)用的架構(gòu)來說坤候,復(fù)雜度不亞于服務(wù)端,但側(cè)重點(diǎn)和入手點(diǎn)卻跟服務(wù)端不太一樣企蹭。比如客戶端應(yīng)用就不需要考慮類似C10K的問題白筹,正常的app就根本不需要考慮智末。
這系列文章我會(huì)主要專注在iOS應(yīng)用架構(gòu)方面,很多方案也是基于iOS技術(shù)棧的特點(diǎn)而建立的徒河。因?yàn)槲覀€(gè)人不是很喜歡寫Java系馆,所以Android這邊的我就不太了解了。如果你是Android開發(fā)者顽照,你可以側(cè)重看我提出的一些架構(gòu)思想役耕,畢竟不管做什么严沥,思路是相通的,實(shí)現(xiàn)手段不同罷了。
當(dāng)我們討論客戶端應(yīng)用架構(gòu)的時(shí)候拆撼,我們?cè)谟懻撌裁矗?/h1>
其實(shí)市面上大部分應(yīng)用不外乎就是顛過來倒過去地做以下這些事情:
簡(jiǎn)單來說就是調(diào)API锌云,展示頁(yè)面郭变,然后跳轉(zhuǎn)到別的地方再調(diào)API拓巧,再展示頁(yè)面。
那這特么有毛好架構(gòu)的思币?
非也鹿响,非也。 ---- 包不同 《天龍八部》
App確實(shí)就是主要做這些事情谷饿,但是支撐這些事情的基礎(chǔ)惶我,就是做架構(gòu)要考慮的事情。
- 調(diào)用網(wǎng)絡(luò)API
- 頁(yè)面展示
- 數(shù)據(jù)的本地持久化
- 動(dòng)態(tài)部署方案
上面這四大點(diǎn)各墨,稍微細(xì)說一下就是:
- 如何讓業(yè)務(wù)開發(fā)工程師方便安全地調(diào)用網(wǎng)絡(luò)API指孤?然后盡可能保證用戶在各種網(wǎng)絡(luò)環(huán)境下都能有良好的體驗(yàn)?
- 頁(yè)面如何組織贬堵,才能盡可能降低業(yè)務(wù)方代碼的耦合度恃轩?盡可能降低業(yè)務(wù)方開發(fā)界面的復(fù)雜度,提高他們的效率黎做?
- 當(dāng)數(shù)據(jù)有在本地存取的需求的時(shí)候叉跛,如何能夠保證數(shù)據(jù)在本地的合理安排?如何盡可能地減小性能消耗蒸殿?
- iOS應(yīng)用有審核周期筷厘,如何能夠通過不發(fā)版本的方式展示新的內(nèi)容給用戶?如何修復(fù)緊急bug宏所?
上面幾點(diǎn)是針對(duì)App說的酥艳,下面還有一些是針對(duì)團(tuán)隊(duì)說的:
- 收集用戶數(shù)據(jù),給產(chǎn)品和運(yùn)營(yíng)提供參考
- 合理地組織各業(yè)務(wù)方開發(fā)的業(yè)務(wù)模塊爬骤,以及相關(guān)基礎(chǔ)模塊
- 每日app的自動(dòng)打包充石,提供給QA工程師的測(cè)試工具
一時(shí)半會(huì)兒我還是只能想到上面這三點(diǎn),事實(shí)上應(yīng)該還會(huì)有很多霞玄,想不起來了骤铃。
所以當(dāng)我們討論客戶端應(yīng)用架構(gòu)的時(shí)候拉岁,我們討論的差不多就是這些問題。
這系列文章要回答那些問題惰爬?
這系列文章主要是回答以下這些問題:
- 網(wǎng)絡(luò)層設(shè)計(jì)方案喊暖?設(shè)計(jì)網(wǎng)絡(luò)層時(shí)要考慮哪些問題?對(duì)網(wǎng)絡(luò)層做優(yōu)化的時(shí)候撕瞧,可以從哪些地方入手陵叽?
- 頁(yè)面的展示、調(diào)用和組織都有哪些設(shè)計(jì)方案丛版?我們做這些方案的時(shí)候都要考慮哪些問題咨跌?
- 本地持久化層的設(shè)計(jì)方案都有哪些??jī)?yōu)劣勢(shì)都是什么硼婿?不同方案間要注意的問題分別都是什么?
- 要實(shí)現(xiàn)動(dòng)態(tài)部署禽车,都有哪些方案寇漫?不同方案之間的優(yōu)劣點(diǎn),他們的側(cè)重點(diǎn)殉摔?
本文要回答那些問題州胳?
上面細(xì)分出來的四個(gè)問題,我會(huì)分別在四篇文章里面寫逸月。那么這篇文章就是來講一些通識(shí)啥的栓撞,也是開個(gè)坑給大家討論通識(shí)問題的。
架構(gòu)設(shè)計(jì)的方法
所有事情最難的時(shí)候都是開始做的時(shí)候碗硬,當(dāng)你開始著手設(shè)計(jì)并實(shí)現(xiàn)某一層的架構(gòu)乃至整個(gè)app的架構(gòu)的時(shí)候瓤湘,很有可能會(huì)出現(xiàn)暫時(shí)的無從下手的情況。以下方法論是我這些年總結(jié)出來的經(jīng)驗(yàn)恩尾,每個(gè)架構(gòu)師也一定都有一套自己的方法論弛说,但一樣的是,不管你采用什么方法翰意,全局觀木人、高度的代碼審美能力、靈活使用各種設(shè)計(jì)模式一定都是貫穿其中的冀偶。歡迎各位在評(píng)論區(qū)討論醒第。
第一步:搞清楚要解決哪些問題,并找到解決這些問題的充要條件
你必須得清楚你要做什么进鸠,業(yè)務(wù)方希望要什么稠曼。而不是為了架構(gòu)而架構(gòu),也不是為了體驗(yàn)新技術(shù)而改架構(gòu)方案堤如。以前是MVC蒲列,最近流行MVVM窒朋,如果過去的MVC是個(gè)好架構(gòu),沒什么特別大的缺陷蝗岖,就不要推倒然后搞成MVVM侥猩。
關(guān)于充要條件我也要說明一下,有的時(shí)候系統(tǒng)提供的函數(shù)是需要額外參數(shù)的抵赢,比如read函數(shù)欺劳。還有翻頁(yè)的時(shí)候,當(dāng)前頁(yè)碼也是充要條件铅鲤。但對(duì)于業(yè)務(wù)方來說划提,這些充要條件還能夠再縮減。
比如read邢享,需要給出file descriptor鹏往,需要給出buf,需要給出size骇塘。但是對(duì)于業(yè)務(wù)方來說伊履,充要條件就只要file descriptor就夠了。再比如翻頁(yè)款违,其實(shí)業(yè)務(wù)方并不需要記錄當(dāng)前頁(yè)號(hào)唐瀑,你給他暴露一個(gè)loadNextPage
這樣的方法就夠了。
搞清楚對(duì)于業(yè)務(wù)方而言的真正充要條件很重要插爹!這決定了你的架構(gòu)是否足夠易用哄辣。另外,傳的參數(shù)越少赠尾,耦合度相對(duì)而言就越小力穗,你替換模塊或者升級(jí)模塊所花的的代價(jià)就越小。
第二步:?jiǎn)栴}分類气嫁,分模塊
這個(gè)不用多說了吧睛廊。
第三步:搞清楚各問題之間的依賴關(guān)系,建立好模塊交流規(guī)范并設(shè)計(jì)模塊
關(guān)鍵在于建立一套統(tǒng)一的交流規(guī)范杉编。這一步很能夠體現(xiàn)架構(gòu)師在軟件方面的價(jià)值觀超全,雖然存在一定程度上的好壞優(yōu)劣(比如胖Model和瘦Model),但既然都是架構(gòu)師了邓馒,基本上是不會(huì)設(shè)計(jì)出明顯很爛的方案的嘶朱,除非這架構(gòu)師還不夠格。所以這里是架構(gòu)師價(jià)值觀輸出的一個(gè)窗口光酣,從這一點(diǎn)我們是能夠看出架構(gòu)師的素質(zhì)的疏遏。
另外要注意的是,一定是建立一套統(tǒng)一的交流規(guī)范,不是兩套财异,不是多套倘零。你要堅(jiān)持你的價(jià)值觀,不要搖擺不定戳寸。要是搞出各種五花八門的規(guī)范出來呈驶,一方面有不切實(shí)際的炫技嫌疑,另一方面也會(huì)帶來后續(xù)維護(hù)的災(zāi)難疫鹊。
第四步:推演預(yù)測(cè)一下未來可能的走向袖瞻,必要時(shí)添加新的模塊,記錄更多的基礎(chǔ)數(shù)據(jù)以備未來之需
很多稱職的架構(gòu)師都會(huì)在這時(shí)候考慮架構(gòu)未來的走向拆吆,以及考慮做完這一輪架構(gòu)之后聋迎,接下來要做的事情。一個(gè)好的架構(gòu)雖然是功在當(dāng)代利在千秋的工程枣耀,但絕對(duì)不是一個(gè)一勞永逸的工程霉晕。軟件是有生命的,你做出來的架構(gòu)決定了這個(gè)軟件它這一生是坎坷還是幸福捞奕。
第五步:先解決依賴關(guān)系中最基礎(chǔ)的問題娄昆,實(shí)現(xiàn)基礎(chǔ)模塊,然后再用基礎(chǔ)模塊堆疊出整個(gè)架構(gòu)
這一步也是驗(yàn)證你之前的設(shè)計(jì)是否合理的一步缝彬,隨著這一步的推進(jìn),你很有可能會(huì)遇到需要對(duì)架構(gòu)進(jìn)行調(diào)整的情況哺眯。這個(gè)階段一定要吹毛求疵高度負(fù)責(zé)地去開發(fā)谷浅,不要得過且過,發(fā)現(xiàn)架構(gòu)有問題就及時(shí)調(diào)整奶卓。否則以后調(diào)整的成本就非常之大了一疯。
第六步:打點(diǎn),跑單元測(cè)試夺姑,跑性能測(cè)試墩邀,根據(jù)數(shù)據(jù)去優(yōu)化對(duì)應(yīng)的地方
你得用這些數(shù)據(jù)去向你的boss邀功,你也得用這些數(shù)據(jù)去不斷調(diào)整你的架構(gòu)盏浙。
總而言之就是要遵循這些原則:自頂向下設(shè)計(jì)
(1眉睹,2,3废膘,4步)竹海,自底向上實(shí)現(xiàn)
(5),先測(cè)量丐黄,后優(yōu)化
(6)斋配。
什么樣的架構(gòu)師是好架構(gòu)師?
- 每天都在學(xué)習(xí),新技術(shù)新思想上手速度快艰争,理解速度快
做不到這點(diǎn)坏瞄,你就是碼農(nóng)
- 業(yè)務(wù)出身,或者至少非常熟悉公司所處行業(yè)或者本公司的業(yè)務(wù)
做不到這點(diǎn)甩卓,你就是運(yùn)維
- 熟悉軟件工程的各種規(guī)范鸠匀,踩過無數(shù)坑。不會(huì)為了完成需求不擇手段猛频,不推崇quick & dirty
做不到這點(diǎn)狮崩,你比較適合去競(jìng)爭(zhēng)對(duì)手那兒當(dāng)工程師
- 及時(shí)承認(rèn)錯(cuò)誤,不要覺得承認(rèn)錯(cuò)誤會(huì)有損你架構(gòu)師的身份
做不到這點(diǎn)鹿寻,公關(guān)行業(yè)比較適合你
- 不為了炫技而炫技
做不到這點(diǎn)睦柴,你就是高中編程愛好者
- 精益求精
做不到這點(diǎn),(我想了好久毡熏,但我還是不知道你適合去干什么坦敌。)
什么樣的架構(gòu)叫好架構(gòu)?
- 代碼整齊痢法,分類明確狱窘,沒有common,沒有core
- 不用文檔财搁,或很少文檔蘸炸,就能讓業(yè)務(wù)方上手
- 思路和方法要統(tǒng)一,盡量不要多元
- 沒有橫向依賴尖奔,萬不得已不出現(xiàn)跨層訪問
- 對(duì)業(yè)務(wù)方該限制的地方有限制搭儒,該靈活的地方要給業(yè)務(wù)方創(chuàng)造靈活實(shí)現(xiàn)的條件
- 易測(cè)試,易拓展
- 保持一定量的超前性
- 接口少提茁,接口參數(shù)少
- 高性能
以上是我判斷一個(gè)架構(gòu)是不是好架構(gòu)的標(biāo)準(zhǔn)淹禾,這是根據(jù)重要性來排列的≤畋猓客戶端架構(gòu)跟服務(wù)端架構(gòu)要考慮的問題和側(cè)重點(diǎn)是有一些區(qū)別的铃岔。下面我會(huì)針對(duì)每一點(diǎn)詳細(xì)講解一下:
1.代碼整齊,分類明確峭火,沒有common毁习,沒有core
代碼整齊是每一個(gè)工程師的基本素質(zhì),先不說你搞定這個(gè)問題的方案有多好卖丸,解決速度有多快蜓洪,如果代碼不整齊,一切都白搭坯苹。因?yàn)槟愕拇a是要給別人看的隆檀,你自己也要看。如果哪一天架構(gòu)有修改,正好改到這個(gè)地方恐仑,你很容易自己都看不懂泉坐。另外,破窗理論提醒我們裳仆,如果代碼不整齊分類不明確腕让,整個(gè)架構(gòu)會(huì)隨著一次一次的拓展而越來越混亂。
分類明確的字面意思大家一定都了解歧斟,但還有一個(gè)另外的意思纯丸,那就是:不要讓一個(gè)類或者一個(gè)模塊做兩種不同的事情
。如果有類或某模塊做了兩種不同的事情静袖,一方面不適合未來拓展觉鼻,另一方面也會(huì)造成分類困難。
不要搞Common队橙,Core這些東西坠陈。每家公司的架構(gòu)代碼庫(kù)里面,最惡心的一定是這兩個(gè)名字命名的文件夾捐康,我這么說一定不會(huì)錯(cuò)仇矾。不要開Common,Core這樣的文件夾解总,開了之后后來者一定會(huì)把這個(gè)地方搞得一團(tuán)糟贮匕,最終變成Common也不Common,Core也不Core花枫。要記住刻盐,架構(gòu)是不斷成長(zhǎng)的,是會(huì)不斷變化的乌昔。不是每次成長(zhǎng)每次變化,都是由你去實(shí)現(xiàn)的壤追。如果真有什么東西特別小磕道,那就索性為了他單獨(dú)開辟一個(gè)模塊就好了,小就小點(diǎn)行冰,關(guān)鍵是要有序溺蕉。
2.不用文檔,或很少文檔悼做,就能讓業(yè)務(wù)方上手
誰特么會(huì)去看文檔啊疯特,業(yè)務(wù)方他們已經(jīng)被產(chǎn)品經(jīng)理逼得很忙了。所以你要盡可能讓你的API名字可讀性強(qiáng)肛走,對(duì)于iOS來說漓雅,objc這門語言的特性把這個(gè)做到了極致,函數(shù)名長(zhǎng)就長(zhǎng)一點(diǎn),不要緊邻吞。
好的函數(shù)名:
- (NSDictionary *)exifDataOfImage:(UIImage *)image atIndexPath:(NSIndexPath *)indexPath;
壞的函數(shù)名:
- (id)exifData:(UIImage *)image position:(id)indexPath callback:(id<ErrorDelegate>)delegate;
為什么壞组题?
1. 不要直接返回id或者傳入id,實(shí)在不行抱冷,用id<protocol>也比id好崔列。如果連這個(gè)都做不到,你要好好考慮你的架構(gòu)是不是有問題旺遮。
2. 要告知業(yè)務(wù)方要傳的東西是什么赵讯,比如要傳Image,那就寫上ofImage耿眉。如果要傳位置边翼,那就要寫上IndexPath,而不是用position這么籠統(tǒng)的東西
3. 沒有任何理由要把delegate作為參數(shù)傳進(jìn)去跷敬,一定不會(huì)有任何情況不得不這么做的讯私。而且delegate這個(gè)參數(shù)根本不是這個(gè)函數(shù)要解決的問題的充要條件,如果你發(fā)現(xiàn)你不得不這么做西傀,那一定是架構(gòu)有問題斤寇!
3.思路和方法要統(tǒng)一,盡量不要多元
解決一個(gè)問題會(huì)有很多種方案拥褂,但是一旦確定了一種方案娘锁,就不要在另一個(gè)地方采用別的方案了。也就是做架構(gòu)的時(shí)候饺鹃,你得時(shí)刻記住當(dāng)初你決定要處理這樣類型的問題的方案是什么莫秆,以及你的初衷是什么,不要搖擺不定悔详。
另外镊屎,你當(dāng)初設(shè)立這個(gè)模塊一定是有想法有原因的,要記錄下你的解決思路茄螃,不要到時(shí)候換個(gè)地方你又靈光一現(xiàn)啥的缝驳,引入了其他方案,從而導(dǎo)致異構(gòu)归苍。
要是一個(gè)框架里面解決同一種類似的問題有各種五花八門的方法或者類用狱,我覺得做這個(gè)架構(gòu)的架構(gòu)師一定是自己都沒想清楚就開始搞了。
4.沒有橫向依賴拼弃,萬不得已不出現(xiàn)跨層訪問
沒有橫向依賴是很重要的夏伊,這決定了你將來要對(duì)這個(gè)架構(gòu)做修補(bǔ)所需要的成本有多大。要做到?jīng)]有橫向依賴吻氧,這是很考驗(yàn)架構(gòu)師的模塊分類能力和是否熟悉業(yè)務(wù)的溺忧。
跨層訪問是指數(shù)據(jù)流向了跟自己沒有對(duì)接關(guān)系的模塊咏连。有的時(shí)候跨層訪問是不可避免的,比如網(wǎng)絡(luò)底層里面信號(hào)從2G變成了3G變成了4G砸狞,這是有可能需要跨層通知到View的捻勉。但這種情況不多,一旦出現(xiàn)就要想盡一切辦法在本層搞定或者交給上層或者下層搞定刀森,盡量不要出現(xiàn)跨層的情況踱启。跨層訪問同樣也會(huì)增加耦合度研底,當(dāng)某一層需要整體替換的時(shí)候埠偿,牽涉面就會(huì)很大。
5.對(duì)業(yè)務(wù)方該限制的地方有限制榜晦,該靈活的地方要給業(yè)務(wù)方創(chuàng)造靈活實(shí)現(xiàn)的條件
把這點(diǎn)做好冠蒋,很依賴于架構(gòu)師的經(jīng)驗(yàn)。架構(gòu)師必須要有能力區(qū)分哪些情況需要限制靈活性乾胶,哪些情況需要?jiǎng)?chuàng)造靈活性抖剿。比如對(duì)于Core Data技術(shù)棧來說,ManagedObject理論上是可以出現(xiàn)在任何地方的识窿,那就意味著任何地方都可以修改ManagedObject斩郎,這就導(dǎo)致ManagedObjectContext在同步修改的時(shí)候把各種不同來源的修改同步進(jìn)去。這時(shí)候就需要限制靈活性喻频,只對(duì)外公開一個(gè)修改接口缩宜,不暴露任何ManagedObject在外面。
如果是設(shè)計(jì)一個(gè)ABTest相關(guān)的API的時(shí)候甥温,我們又希望增加它的靈活性锻煌。使得業(yè)務(wù)方不光可以通過Target-Action的模式實(shí)現(xiàn)ABtest,也要可以通過Block的方式實(shí)現(xiàn)ABTest姻蚓,要盡可能滿足靈活性宋梧,減少業(yè)務(wù)方的使用成本。
6.易測(cè)試易拓展
老生常談狰挡,要實(shí)現(xiàn)易測(cè)試易拓展捂龄,那就要提高模塊化程度,盡可能減少依賴關(guān)系圆兵,便于mock跺讯。另外枢贿,如果是高度模塊化的架構(gòu)殉农,拓展起來將會(huì)是一件非常容易的事情。
7.保持一定量的超前性
這一點(diǎn)能看出架構(gòu)師是否關(guān)注行業(yè)動(dòng)態(tài)局荚,是否能準(zhǔn)確把握技術(shù)走向超凳。保持適度的技術(shù)上的超前性愈污,能夠使得你的架構(gòu)更新變得相對(duì)輕松。
另外轮傍,這里的超前性也不光是技術(shù)上的暂雹,還有產(chǎn)品上的。誰說架構(gòu)師就不需要跟產(chǎn)品經(jīng)理打交道了创夜,沒事多跟產(chǎn)品經(jīng)理聊聊天杭跪,聽聽他對(duì)產(chǎn)品未來走向的暢想,你就可以在合理的地方為他的暢想留一條路子驰吓。同時(shí)涧尿,在創(chuàng)業(yè)公司的環(huán)境下,很多產(chǎn)品需求其實(shí)只是為了趕產(chǎn)品進(jìn)度而產(chǎn)生的妥協(xié)方案檬贰,最后還是會(huì)轉(zhuǎn)到正軌的姑廉。這時(shí)候業(yè)務(wù)方可以不實(shí)現(xiàn)轉(zhuǎn)到正規(guī)的方案,但是架構(gòu)這邊翁涤,是一定要為這種可預(yù)知的改變做準(zhǔn)備的桥言。
8.接口少,接口參數(shù)少
越少的接口越少的參數(shù)葵礼,就能越降低業(yè)務(wù)方的使用成本号阿。當(dāng)然,充要條件還是要滿足的章咧,如何在滿足充要條件的情況下盡可能地減少接口和參數(shù)數(shù)量倦西,這就能看出架構(gòu)師的功力有多深厚了。
9.高性能
為什么高性能排在最后一位赁严?
高性能非常重要扰柠,但是在客戶端架構(gòu)中,它不是第一考慮因素疼约。原因有下:
- 客戶端業(yè)務(wù)變化非常之快卤档,做架構(gòu)時(shí)首要考慮因素應(yīng)當(dāng)是便于業(yè)務(wù)方快速滿足產(chǎn)品需求,因此需要盡可能提供簡(jiǎn)單易用效果好的接口給業(yè)務(wù)方程剥,而不是提供高性能的接口給業(yè)務(wù)方劝枣。
- 蘋果平臺(tái)的性能非常之棒,正常情況下很少會(huì)出現(xiàn)由于性能不夠?qū)е碌挠脩趔w驗(yàn)問題织鲸。
- 蘋果平臺(tái)的優(yōu)化手段相對(duì)有限舔腾,甚至于有些時(shí)候即便動(dòng)用了無所不用其極的手段乃至不擇手段犧牲了穩(wěn)定性,性能提高很有可能也只不過是100ms到90ms的差距搂擦。10%的性能提升對(duì)于服務(wù)端來說很不錯(cuò)了稳诚,因?yàn)榉?wù)端動(dòng)不動(dòng)就是幾十萬上百萬的訪問量,幾十萬上百萬個(gè)10ms是很可觀的瀑踢。但是對(duì)于客戶端的用戶來說扳还,他無法感知這10ms的差別才避,如果從10s優(yōu)化成9s用戶還是有一定感知的,但是100ms變90ms氨距,我覺得吧桑逝,還是別折騰了。
但是俏让!不重要不代表用不著去做楞遏,關(guān)于性能優(yōu)化的東西,我會(huì)對(duì)應(yīng)放到各系列文章里面去首昔。比如網(wǎng)絡(luò)層優(yōu)化橱健,那就會(huì)在網(wǎng)絡(luò)層方案的那篇文章里面去寫,對(duì)應(yīng)每層架構(gòu)都有每層架構(gòu)的不同優(yōu)化方案沙廉,我都會(huì)在各自文章里面一一細(xì)說拘荡。
2015-4-2 11:28 補(bǔ): 關(guān)于架構(gòu)分層?
昨晚上志豪看了這篇文章之后說撬陵,看到你這個(gè)題目本來我是期望看到關(guān)于架構(gòu)分層相關(guān)的東西的珊皿,但是你沒寫。
嗯巨税,確實(shí)沒寫蟋定,當(dāng)時(shí)沒寫的原因是感覺這個(gè)沒什么好寫的。前面談?wù)摰郊軜?gòu)的方法的時(shí)候草添,關(guān)于問題分類分模塊這一步時(shí)驶兜,架構(gòu)分層也屬于這一部分,給我一筆帶過了远寸。
既然志豪提出來了這個(gè)問題抄淑,我想可能大家關(guān)于這個(gè)也會(huì)有一些想法和問題,那么我就在這兒講講吧驰后。
其實(shí)分層這種東西肆资,真沒啥技術(shù)含量,全憑架構(gòu)師的經(jīng)驗(yàn)和素質(zhì)灶芝。
我們常見的分層架構(gòu)郑原,有三層架構(gòu)的:展現(xiàn)層、業(yè)務(wù)層夜涕、數(shù)據(jù)層犯犁。也有四層架構(gòu)的:展現(xiàn)層、業(yè)務(wù)層女器、網(wǎng)絡(luò)層酸役、本地?cái)?shù)據(jù)層。這里說三層
晓避、四層
簇捍,跟TCP/IP所謂的五層或者七層不是同一種概念。再具體說就是:你這個(gè)架構(gòu)在邏輯上是幾層那就幾層俏拱,具體每一層叫什么暑塑,做什么,沒有特定的規(guī)范锅必。這主要是針對(duì)模塊分類而言的事格。
也有說MVC架構(gòu),MVVM架構(gòu)的搞隐,這種層次劃分驹愚,主要是針對(duì)數(shù)據(jù)流動(dòng)的方向而言的。
在實(shí)際情況中劣纲,針對(duì)數(shù)據(jù)流動(dòng)方向做的設(shè)計(jì)和針對(duì)模塊分類做的設(shè)計(jì)是會(huì)放在一起的逢捺,也就是說,一個(gè)MVC架構(gòu)可以是四層:展現(xiàn)層癞季、業(yè)務(wù)層劫瞳、網(wǎng)絡(luò)層、本地?cái)?shù)據(jù)層绷柒。
那么志于,為什么我要說這個(gè)?
大概在五六年前废睦,業(yè)界很流行三層架構(gòu)
這個(gè)術(shù)語伺绽。然后各種文檔資料漫天的三層架構(gòu)
,并且喜歡把它與MVC
放在一起說嗜湃,MVC三層架構(gòu)/三層架構(gòu)MVC
奈应,以至于很多人就會(huì)認(rèn)為三層架構(gòu)
就是MVC
,MVC
就是三層架構(gòu)
购披。其實(shí)不是的钥组。三層架構(gòu)
里面其實(shí)沒有Controller
的概念,而且三層架構(gòu)描述的側(cè)重點(diǎn)是模塊之間的邏輯關(guān)系今瀑。MVC
有Controller
的概念程梦,它描述的側(cè)重點(diǎn)在于數(shù)據(jù)流動(dòng)方向。
好橘荠,為什么流行起來的是
三層架構(gòu)
屿附,而不是四層架構(gòu)
或五層架構(gòu)
?
因?yàn)樗械哪K角色只會(huì)有三種:數(shù)據(jù)管理者
哥童、數(shù)據(jù)加工者
挺份、數(shù)據(jù)展示者
,意思也就是贮懈,籠統(tǒng)說來匀泊,軟件只會(huì)有三層优训,每一層扮演一個(gè)角色。其他的第四層第五層各聘,一般都是這三層里面的其中之一分出來的揣非,最后都能歸納進(jìn)這三層的某一層中去,所以用三層架構(gòu)
來描述就比較普遍躲因。
那么我們?cè)趺醋龇謱樱?/p>
應(yīng)該如何做分層早敬,不是在做架構(gòu)的時(shí)候一開始就考慮的問題。雖然我們要按照自頂向下的設(shè)計(jì)方式來設(shè)計(jì)架構(gòu)大脉,但是一般情況下不適合直接從三層開始搞监。一般都是先確定所有要解決的問題,先確定都有哪些模塊镰矿,然后再基于這些模塊再往下細(xì)化設(shè)計(jì)琐驴。然后再把這些列出來的問題和模塊做好分類。分類之后不出意外大多數(shù)都是三層秤标。如果發(fā)現(xiàn)某一層特別龐大棍矛,那就可以再拆開來變成四層,變成五層抛杨。
舉個(gè)例子:你要設(shè)計(jì)一個(gè)即時(shí)通訊的服務(wù)端架構(gòu)够委,怎么分層?
記住怖现,不要一上來就把三層架構(gòu)
的規(guī)范套上去茁帽,這樣做是做不出好架構(gòu)的。
你要先確定都需要解決哪些問題屈嗤。這里只是舉例子潘拨,我隨意列出一點(diǎn)意思意思就好了:
- 要解決用戶登錄、退出的問題
- 解決不同用戶間數(shù)據(jù)交流的問題
- 解決用戶數(shù)據(jù)存儲(chǔ)的問題
- 如果是多臺(tái)服務(wù)器的集群饶号,就要解決用戶連接的尋址問題
解決第一個(gè)問題需要一個(gè)鏈接管理模塊铁追,鏈接管理模塊一般是通過鏈接池來實(shí)現(xiàn)。 解決第二個(gè)問題需要有一個(gè)數(shù)據(jù)交換模塊茫船,從A接收來的數(shù)據(jù)要給到B琅束,這個(gè)事情由這個(gè)模塊來做。 解決第三個(gè)問題需要有個(gè)數(shù)據(jù)庫(kù)算谈,如果是服務(wù)于大量用戶涩禀,那么就需要一個(gè)緩沖區(qū),只有當(dāng)需要存儲(chǔ)的數(shù)據(jù)達(dá)到一定量時(shí)才執(zhí)行寫操作然眼。 解決第四個(gè)問題可以有幾種解決方案艾船,一個(gè)是集群中有那么幾臺(tái)服務(wù)器作為尋路服務(wù)器,所有尋路的服務(wù)交給那幾臺(tái)去做,那么你需要開發(fā)一個(gè)尋路服務(wù)的Daemon屿岂〖纾或者用廣播方式尋路,但如果尋路頻次非常高爷怀,會(huì)造成集群內(nèi)部網(wǎng)絡(luò)負(fù)載特別大阻肩。這是你要權(quán)衡的地方,目前流行的思路是去中心化霉撵,那么要解決網(wǎng)絡(luò)負(fù)載的問題,你就可以考慮配置一個(gè)緩存洪囤。
于是我們有了這些模塊:
鏈接管理徒坡、數(shù)據(jù)交換、數(shù)據(jù)庫(kù)及其配套模塊瘤缩、尋路模塊
做到這里還遠(yuǎn)遠(yuǎn)沒有結(jié)束喇完,你要繼續(xù)針對(duì)這四個(gè)模塊繼續(xù)往下細(xì)分,直到足夠小為止剥啤。但是這里只是舉例子锦溪,所以就不往下深究了。
另外府怯,我要提醒你的是刻诊,直到這時(shí),還是跟幾層架構(gòu)毫無關(guān)系的牺丙。當(dāng)你把所有模塊都找出來之后则涯,就要開始整理你的這些模塊,很有可能架構(gòu)圖就是這樣:
然后這些模塊分完之后你看一下圖冲簿,嗯粟判,1、2峦剔、3档礁,一共三層,所以那就是三層架構(gòu)
啦吝沫。在這里最消耗腦力最考驗(yàn)架構(gòu)師功力的地方就在于:找到所有需要的模塊
, 把模塊放在該放的地方
這個(gè)例子側(cè)重點(diǎn)在于如何分層呻澜,性能優(yōu)化、數(shù)據(jù)交互規(guī)范和包協(xié)議惨险、數(shù)據(jù)采集等其他一系列必要的東西都沒有放進(jìn)去易迹,但看到這里,相信你應(yīng)該了解架構(gòu)師是怎么對(duì)待分層問題的了吧平道?
對(duì)的睹欲,答案就是沒有分層。所謂的分層都是出架構(gòu)圖之后的事情了。所以你看別的架構(gòu)師在演講的時(shí)候窘疮,上來第一句話差不多都是:"這個(gè)架構(gòu)分為以下幾層..."袋哼。但考慮分層的問題的時(shí)機(jī)絕對(duì)不是一開始就考慮的。另外闸衫,模塊一定要把它設(shè)計(jì)得獨(dú)立性強(qiáng)涛贯,這其實(shí)是門藝術(shù)活。
另外蔚出,這雖然是服務(wù)端架構(gòu)弟翘,但是思路跟客戶端架構(gòu)是一樣的乡话,側(cè)重點(diǎn)不同罷了萎坷。之所以不拿客戶端架構(gòu)舉例子,是因?yàn)檫@方面的客戶端架構(gòu)蘋果已經(jīng)幫你做好了絕大部分事情安吁,沒剩下什么值得說的了趋翻。
2015-4-5 12:15 補(bǔ):關(guān)于Common文件夾睛琳?
評(píng)論區(qū)MatrixHero提到一點(diǎn):
關(guān)于common文件夾的問題,僅僅是文件夾而已踏烙,別無他意师骗。如果后期維護(hù)出了代碼混亂可能是因?yàn)椋头?wù)器溝通協(xié)議不統(tǒng)一讨惩,或代碼review不及時(shí)辟癌。應(yīng)該有專人維護(hù)公共類。
這是針對(duì)我前面提出的不要Common荐捻,不要Core
而言的愿待,為什么我建議大家不要開Common文件夾?我打算分幾種情況給大家解釋一下靴患。
一般情況下仍侥,我們都會(huì)有一些屬于這個(gè)項(xiàng)目的公共類,比如取定位坐標(biāo)鸳君,比如圖像處理农渊。這些模塊可能非常小,就h和m兩個(gè)文件或颊。單獨(dú)拎出來成為一個(gè)模塊感覺不夠格砸紊,但是又不屬于其他任何一個(gè)模塊。于是大家很有可能就會(huì)把它們放入Common里面囱挑,我目前見到的大多數(shù)工程和大多數(shù)文檔里面的代碼都喜歡這么做醉顽。在當(dāng)時(shí)來看,這么做看不出什么問題平挑,但關(guān)鍵在于:軟件是有生命游添,會(huì)成長(zhǎng)的
系草。當(dāng)時(shí)分出來的小模塊,很有可能會(huì)隨著業(yè)務(wù)的成長(zhǎng)唆涝,逐漸發(fā)展成大模塊找都,發(fā)展成大模塊后,可以再把它從Common移出來單獨(dú)成立一個(gè)模塊廊酣。這個(gè)在理論上是沒有任何問題的能耻,然而在實(shí)際操作過程中,工程師在拓張這個(gè)小模塊的時(shí)候亡驰,不太容易會(huì)去考慮橫向依賴
的問題晓猛,因?yàn)楫?dāng)時(shí)這些模塊都在Common里面,直接進(jìn)行互相依賴是非常符合直覺的凡辱,而且也不算是不遵守規(guī)范戒职。然而要注意的是,這才是Commom代碼混亂
的罪魁禍?zhǔn)咨访#珻ommon文件夾縱容了不精心管理依賴
的做法帕涌。當(dāng)Common里面的模塊依賴關(guān)系變得復(fù)雜摄凡,再想要移出來單獨(dú)成立一個(gè)模塊续徽,就不是當(dāng)初設(shè)置Common時(shí)想的等規(guī)模大了再移除也不遲
那么簡(jiǎn)單了。
另外亲澡,
Common
有的時(shí)候也不僅僅是一個(gè)文件夾钦扭。
在使用Cocoapods來管理項(xiàng)目庫(kù)的時(shí)候,Common
往往就是一個(gè)pod床绪。這個(gè)pod里面會(huì)有A/B/C/D/E這些函數(shù)集或小模塊客情。如果要新開一個(gè)app或者Demo,勢(shì)必會(huì)使用到Common這個(gè)pod癞己,這么做膀斋,往往會(huì)把不需要包含的代碼也包含進(jìn)去,我對(duì)項(xiàng)目有高度潔癖痹雅,這種情況會(huì)讓我覺得非常不舒服仰担。
舉個(gè)例子:早年安居客的app還不是集齊所有新房
、二手房
绩社、租房
業(yè)務(wù)的摔蓝。當(dāng)你剛開始寫新房
這個(gè)app的時(shí)候,創(chuàng)建了一個(gè)Common這個(gè)pod愉耙,這里面包含了一些對(duì)于新房
來說比較Common的代碼贮尉,也包含了對(duì)于這個(gè)app來說比較Common的代碼。過了半年或者一年朴沿,你要開始二手房
這個(gè)app猜谚,我覺得大多數(shù)人都會(huì)選擇讓二手房
也包含這個(gè)Common败砂,于是這個(gè)Common很有可能自己走上另一條發(fā)展的道路。等到了租房
這個(gè)業(yè)務(wù)要開app的時(shí)候龄毡,Common已經(jīng)非常之龐大吠卷,相信這時(shí)候的你也不會(huì)去想整理Common的事情了,先把租房
搞定沦零,于是Common最終就變成了一坨屎祭隔。
就對(duì)于上面的例子來說,還有一個(gè)要考慮的是路操,分出來的三個(gè)業(yè)務(wù)很有可能會(huì)有三個(gè)Common疾渴,假設(shè)三個(gè)Common里面都有公共的功能,交給了三個(gè)團(tuán)隊(duì)去打理屯仗,如果遇到某個(gè)子模塊需要升級(jí)搞坝,那么三個(gè)Common里面的這個(gè)子模塊都要去同步升級(jí),這是個(gè)很不效率的事情魁袜。另外桩撮,很有可能三個(gè)Common到最后發(fā)展成彼此不兼容,但是代碼相似度非常之高峰弹,這個(gè)在架構(gòu)上店量,是屬于分類條理不清
。
就在去年年中的時(shí)候鞠呈,安居客決定將三個(gè)業(yè)務(wù)歸并到同一個(gè)App融师。好了,如果你是架構(gòu)師蚁吝,面對(duì)這三個(gè)Common旱爆,你打算怎么辦?要想最快出成果窘茁,那就只好忍受代碼冗余怀伦,趕緊先把架子搭起來再說,否則你面對(duì)的就是剪不斷理還亂的Common山林。此時(shí)Common就已經(jīng)很無奈地變成一坨屎了房待。這樣的Common,你自己說不定也搞不清楚它里面到底都有些什么了捌朴,交給任何一個(gè)人去打理吴攒,他都不敢做徹底的整理的。
還有就是砂蔽,Common本身就是一個(gè)粒度非常大的模塊洼怔。在阿里這樣大規(guī)模的團(tuán)隊(duì)中,即便新開一個(gè)業(yè)務(wù)左驾,都需要在整個(gè)app的環(huán)境下開發(fā)镣隶,為什么极谊?因?yàn)槟K拆分粒度不夠,要想開一個(gè)新業(yè)務(wù)安岂,必須把其他業(yè)務(wù)的代碼以及依賴全部拉下來轻猖,然后再開新入口,你的新業(yè)務(wù)才能進(jìn)行正常的代碼編寫和調(diào)試域那。然而你的新業(yè)務(wù)其實(shí)只依賴首頁(yè)入口咙边、網(wǎng)絡(luò)庫(kù)等這幾個(gè)小模塊,不需要依賴其他那么多的跟你沒關(guān)系的業(yè)務(wù)〈卧保現(xiàn)在每次打開天貓的項(xiàng)目败许,我都要等個(gè)兩三分鐘,這非常之蛋疼淑蔚。
但是大家真的不知道這個(gè)原因嗎市殷?知道了這個(gè)原因,為什么沒人去把這些粒度不夠細(xì)的模塊整理好刹衫?在我看來醋寝,這件事沒人敢做。
- 原來大家用的好好的带迟,手段爛就爛一點(diǎn)音羞,你改了你能保證不出錯(cuò)?
- 這么復(fù)雜的東西邮旷,短期之內(nèi)你肯定搞不好黄选,任務(wù)量和工時(shí)都不好估蝇摸,你leader會(huì)覺得你在騙工時(shí)玩自己的事情婶肩。
- 就算你搞定了,QA這邊肯定再需要做一次全面的回歸測(cè)試貌夕,任務(wù)量極大律歼,難以說服他們配合你的工作。
花這么大的成本只是為了減少開啟項(xiàng)目時(shí)候等待IDE打開時(shí)的那幾分鐘時(shí)間啡专?我想如果我是你leader险毁,我也應(yīng)該不會(huì)批準(zhǔn)你做這樣的事情的。所以们童,與其到了后面吃這個(gè)苦頭畔况,不如一開始做架構(gòu)的時(shí)候就不要設(shè)置Common,到后面就能省力很多慧库。架構(gòu)師的工作為什么是功在當(dāng)代利在千秋跷跪,架構(gòu)師的素質(zhì)為什么對(duì)團(tuán)隊(duì)這么重要?我覺得這里就是一個(gè)最好的體現(xiàn)齐板。
簡(jiǎn)而言之吵瞻,不建議開Common的原因如下:
- Common不僅僅是一個(gè)文件夾葛菇,它也會(huì)是一個(gè)Pod。不管是什么橡羞,在Common里面很容易形成錯(cuò)綜復(fù)雜的小模塊依賴眯停,在模塊成長(zhǎng)過程中,會(huì)縱容工程師不注意依賴的管理卿泽,乃至于將來如果要將模塊拆分出去莺债,會(huì)非常的困難。
- Common本身與細(xì)粒度模塊設(shè)計(jì)的思想背道而馳签夭,屬于一種不合適的偷懶手段九府,在將來業(yè)務(wù)拓張會(huì)成為阻礙。
- 一旦設(shè)置了Common覆致,就等于給地獄之門打開了一個(gè)小縫侄旬,每次業(yè)務(wù)迭代都會(huì)有一些不太好分類的東西放入Common,這就給維護(hù)Common的人帶來了非常大的工作量煌妈,而且這些工作量全都是體力活儡羔,非常容易出錯(cuò)。
那么璧诵,不設(shè)Common會(huì)帶來哪些好處汰蜘?
- 強(qiáng)迫工程師在業(yè)務(wù)拓張的時(shí)候?qū)⒁蕾嚬芾淼氖虑榭紤]進(jìn)去,讓模塊在一開始發(fā)展的時(shí)候就有自己的土壤之宿,成長(zhǎng)空間和靈活度非常大族操。
- 減少各業(yè)務(wù)模塊或者Demo的體積,不需要的模塊不會(huì)由于Common的存在而包含在內(nèi)比被。
- 可維護(hù)性大大提高色难,模塊升級(jí)之后要做的同步工作非常輕松,解放了那個(gè)苦逼的Common維護(hù)者等缀,更多的時(shí)間可以用在更實(shí)質(zhì)的開發(fā)工作上枷莉。
- 符合細(xì)粒度模塊劃分的架構(gòu)思想。
Common的好處只有一個(gè)尺迂,就是前期特別省事兒笤妙。然而它的壞處比好處要多太多。不設(shè)置Common噪裕,再小的模塊再小的代碼也單獨(dú)拎出來蹲盘,最多就是Podfile里面要多寫幾行,多寫幾行最多只花費(fèi)幾分鐘膳音。但若要消除Common所帶來的罪孽召衔,不是這幾分鐘就能搞定的事情。既然不用Common的好處這么多严蓖,那何樂而不為呢薄嫡?
假設(shè)將來你的項(xiàng)目中有一個(gè)類是用來做Location的氧急,哪怕只有兩個(gè)文件,也給他開一個(gè)模塊就叫Location毫深。如果你的項(xiàng)目中有一個(gè)類是用來做ImageProcess的吩坝,那也開一個(gè)模塊就叫ImageProcess。不要都放到Common里面去哑蔫,將來你再開新的項(xiàng)目或者新的業(yè)務(wù)钉寝,用Location就寫依賴Location,用ImageProcess就寫依賴ImageProcess闸迷,不要再依賴Common了嵌纲,這樣你的項(xiàng)目也好管理,管理Common的那個(gè)人日子過得也輕松(這個(gè)人其實(shí)都可以不需要了腥沽,把他的工資加到你頭上不是更好逮走?:D),將來要升級(jí)今阳,顧慮也少师溅。
結(jié)束
一下子挖了個(gè)大坑,在開篇里扯了一些淡盾舌。
嗯墓臭,干貨會(huì)在后續(xù)的系列文章里面撲面而來的!
有任何問題建議直接在評(píng)論區(qū)提問妖谴,這樣后來的人如果有相同的問題窿锉,就能直接找到答案了。提問之前也可以先看看評(píng)論區(qū)有沒有人問過類似問題了膝舅。
所有評(píng)論和問題我都會(huì)在第一時(shí)間回復(fù)嗡载,QQ上我是不回答問題的哈。
評(píng)論系統(tǒng)我用的是Disqus铸史,不定期被墻鼻疮。所以如果你看到文章下面沒有加載出評(píng)論列表怯伊,翻個(gè)墻就有了琳轿。
本文遵守CC-BY。 請(qǐng)保持轉(zhuǎn)載后文章內(nèi)容的完整耿芹,以及文章出處崭篡。本人保留所有版權(quán)相關(guān)權(quán)利。