iOS應(yīng)用架構(gòu)談 開篇
iOS應(yīng)用架構(gòu)談 view層的組織和調(diào)用方案
iOS應(yīng)用架構(gòu)談 網(wǎng)絡(luò)層設(shè)計方案
iOS應(yīng)用架構(gòu)談 動態(tài)部署方案
iOS應(yīng)用架構(gòu)談 本地持久化方案
緣由
之前安居客iOS app的第二版架構(gòu)大部分內(nèi)容是我做的,期間有總結(jié)了一些經(jīng)驗(yàn)虎眨。在將近一年之后清蚀,前同事zzz在微信朋友圈上發(fā)了一個問題:假如問你一個iOS or Android app的架構(gòu)刨摩,你會從哪些方面來說呢幔摸?
當(dāng)時看到這個問題正好在乘公車回家的路上,閑來無聊就答了一把拴驮。在zzz在微信朋友圈上追問了幾個問題之后庸汗,我覺得有必要開個博客專門來講講一些個人見解。
其實(shí)對于iOS客戶端應(yīng)用的架構(gòu)來說山上,復(fù)雜度不亞于服務(wù)端眼耀,但側(cè)重點(diǎn)和入手點(diǎn)卻跟服務(wù)端不太一樣。比如客戶端應(yīng)用就不需要考慮類似C10K的問題佩憾,正常的app就根本不需要考慮哮伟。
這系列文章我會主要專注在iOS應(yīng)用架構(gòu)方面,很多方案也是基于iOS技術(shù)棧的特點(diǎn)而建立的妄帘。因?yàn)槲覀€人不是很喜歡寫Java楞黄,所以Android這邊的我就不太了解了。如果你是Android開發(fā)者抡驼,你可以側(cè)重看我提出的一些架構(gòu)思想鬼廓,畢竟不管做什么,思路是相通的婶恼,實(shí)現(xiàn)手段不同罷了桑阶。
當(dāng)我們討論客戶端應(yīng)用架構(gòu)的時候,我們在討論什么勾邦?
其實(shí)市面上大部分應(yīng)用不外乎就是顛過來倒過去地做以下這些事情:
--------------- --------------- --------------- --------------- | | | | | | | | | 調(diào)用網(wǎng)絡(luò)API | --> | 展現(xiàn)列表 | --> | 選擇列表 | --> | 展現(xiàn)單頁 | | | | | | | | | --------------- --------------- --------------- --------------- ^ | | | | | ------------------------------------------
簡單來說就是調(diào)API蚣录,展示頁面,然后跳轉(zhuǎn)到別的地方再調(diào)API眷篇,再展示頁面萎河。
那這特么有毛好架構(gòu)的?
非也,非也虐杯。 ---- 包不同 《天龍八部》
App確實(shí)就是主要做這些事情玛歌,但是支撐這些事情的基礎(chǔ),就是做架構(gòu)要考慮的事情擎椰。
調(diào)用網(wǎng)絡(luò)API
頁面展示
數(shù)據(jù)的本地持久化
動態(tài)部署方案
上面這四大點(diǎn)支子,稍微細(xì)說一下就是:
如何讓業(yè)務(wù)開發(fā)工程師方便安全地調(diào)用網(wǎng)絡(luò)API?然后盡可能保證用戶在各種網(wǎng)絡(luò)環(huán)境下都能有良好的體驗(yàn)达舒?
頁面如何組織值朋,才能盡可能降低業(yè)務(wù)方代碼的耦合度?盡可能降低業(yè)務(wù)方開發(fā)界面的復(fù)雜度巩搏,提高他們的效率昨登?
當(dāng)數(shù)據(jù)有在本地存取的需求的時候,如何能夠保證數(shù)據(jù)在本地的合理安排贯底?如何盡可能地減小性能消耗丰辣?
iOS應(yīng)用有審核周期,如何能夠通過不發(fā)版本的方式展示新的內(nèi)容給用戶禽捆?如何修復(fù)緊急bug笙什?
上面幾點(diǎn)是針對App說的,下面還有一些是針對團(tuán)隊說的:
收集用戶數(shù)據(jù)胚想,給產(chǎn)品和運(yùn)營提供參考
合理地組織各業(yè)務(wù)方開發(fā)的業(yè)務(wù)模塊得湘,以及相關(guān)基礎(chǔ)模塊
每日app的自動打包,提供給QA工程師的測試工具
一時半會兒我還是只能想到上面這三點(diǎn)顿仇,事實(shí)上應(yīng)該還會有很多,想不起來了摆马。
所以當(dāng)我們討論客戶端應(yīng)用架構(gòu)的時候臼闻,我們討論的差不多就是這些問題。
這系列文章要回答那些問題囤采?
這系列文章主要是回答以下這些問題:
網(wǎng)絡(luò)層設(shè)計方案述呐?設(shè)計網(wǎng)絡(luò)層時要考慮哪些問題?對網(wǎng)絡(luò)層做優(yōu)化的時候蕉毯,可以從哪些地方入手乓搬?
頁面的展示、調(diào)用和組織都有哪些設(shè)計方案代虾?我們做這些方案的時候都要考慮哪些問題进肯?
本地持久化層的設(shè)計方案都有哪些?優(yōu)劣勢都是什么棉磨?不同方案間要注意的問題分別都是什么江掩?
要實(shí)現(xiàn)動態(tài)部署,都有哪些方案?不同方案之間的優(yōu)劣點(diǎn)环形,他們的側(cè)重點(diǎn)策泣?
本文要回答那些問題?
上面細(xì)分出來的四個問題抬吟,我會分別在四篇文章里面寫萨咕。那么這篇文章就是來講一些通識啥的,也是開個坑給大家討論通識問題的火本。
架構(gòu)設(shè)計的方法
所有事情最難的時候都是開始做的時候危队,當(dāng)你開始著手設(shè)計并實(shí)現(xiàn)某一層的架構(gòu)乃至整個app的架構(gòu)的時候,很有可能會出現(xiàn)暫時的無從下手的情況发侵。以下方法論是我這些年總結(jié)出來的經(jīng)驗(yàn)交掏,每個架構(gòu)師也一定都有一套自己的方法論,但一樣的是刃鳄,不管你采用什么方法盅弛,全局觀、高度的代碼審美能力叔锐、靈活使用各種設(shè)計模式一定都是貫穿其中的挪鹏。歡迎各位在評論區(qū)討論。
第一步:搞清楚要解決哪些問題愉烙,并找到解決這些問題的充要條件
你必須得清楚你要做什么讨盒,業(yè)務(wù)方希望要什么。而不是為了架構(gòu)而架構(gòu)步责,也不是為了體驗(yàn)新技術(shù)而改架構(gòu)方案返顺。以前是MVC,最近流行MVVM蔓肯,如果過去的MVC是個好架構(gòu)遂鹊,沒什么特別大的缺陷,就不要推倒然后搞成MVVM蔗包。
關(guān)于充要條件我也要說明一下秉扑,有的時候系統(tǒng)提供的函數(shù)是需要額外參數(shù)的,比如read函數(shù)调限。還有翻頁的時候舟陆,當(dāng)前頁碼也是充要條件。但對于業(yè)務(wù)方來說耻矮,這些充要條件還能夠再縮減秦躯。
比如read,需要給出file descriptor裆装,需要給出buf宦赠,需要給出size陪毡。但是對于業(yè)務(wù)方來說,充要條件就只要file descriptor就夠了勾扭。再比如翻頁毡琉,其實(shí)業(yè)務(wù)方并不需要記錄當(dāng)前頁號,你給他暴露一個loadNextPage
這樣的方法就夠了妙色。
搞清楚對于業(yè)務(wù)方而言的真正充要條件很重要桅滋!這決定了你的架構(gòu)是否足夠易用。另外身辨,傳的參數(shù)越少丐谋,耦合度相對而言就越小,你替換模塊或者升級模塊所花的的代價就越小煌珊。
第二步:問題分類号俐,分模塊
這個不用多說了吧。
第三步:搞清楚各問題之間的依賴關(guān)系定庵,建立好模塊交流規(guī)范并設(shè)計模塊
關(guān)鍵在于建立一套統(tǒng)一的交流規(guī)范吏饿。這一步很能夠體現(xiàn)架構(gòu)師在軟件方面的價值觀,雖然存在一定程度上的好壞優(yōu)劣(比如胖Model和瘦Model)蔬浙,但既然都是架構(gòu)師了猪落,基本上是不會設(shè)計出明顯很爛的方案的,除非這架構(gòu)師還不夠格畴博。所以這里是架構(gòu)師價值觀輸出的一個窗口笨忌,從這一點(diǎn)我們是能夠看出架構(gòu)師的素質(zhì)的。
另外要注意的是俱病,一定是建立一套統(tǒng)一的交流規(guī)范官疲,不是兩套,不是多套亮隙。你要堅持你的價值觀袁余,不要搖擺不定。要是搞出各種五花八門的規(guī)范出來咱揍,一方面有不切實(shí)際的炫技嫌疑,另一方面也會帶來后續(xù)維護(hù)的災(zāi)難棚饵。
第四步:推演預(yù)測一下未來可能的走向煤裙,必要時添加新的模塊,記錄更多的基礎(chǔ)數(shù)據(jù)以備未來之需
很多稱職的架構(gòu)師都會在這時候考慮架構(gòu)未來的走向噪漾,以及考慮做完這一輪架構(gòu)之后硼砰,接下來要做的事情。一個好的架構(gòu)雖然是功在當(dāng)代利在千秋的工程欣硼,但絕對不是一個一勞永逸的工程题翰。軟件是有生命的,你做出來的架構(gòu)決定了這個軟件它這一生是坎坷還是幸福。
第五步:先解決依賴關(guān)系中最基礎(chǔ)的問題豹障,實(shí)現(xiàn)基礎(chǔ)模塊冯事,然后再用基礎(chǔ)模塊堆疊出整個架構(gòu)
這一步也是驗(yàn)證你之前的設(shè)計是否合理的一步,隨著這一步的推進(jìn)血公,你很有可能會遇到需要對架構(gòu)進(jìn)行調(diào)整的情況昵仅。這個階段一定要吹毛求疵高度負(fù)責(zé)地去開發(fā),不要得過且過累魔,發(fā)現(xiàn)架構(gòu)有問題就及時調(diào)整摔笤。否則以后調(diào)整的成本就非常之大了。
第六步:打點(diǎn)垦写,跑單元測試吕世,跑性能測試,根據(jù)數(shù)據(jù)去優(yōu)化對應(yīng)的地方
你得用這些數(shù)據(jù)去向你的boss邀功梯投,你也得用這些數(shù)據(jù)去不斷調(diào)整你的架構(gòu)命辖。
總而言之就是要遵循這些原則:自頂向下設(shè)計
(1,2晚伙,3吮龄,4步),自底向上實(shí)現(xiàn)
(5)咆疗,先測量漓帚,后優(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ù)坑搅荞。不會為了完成需求不擇手段,不推崇quick & dirty
做不到這點(diǎn)框咙,你比較適合去競爭對手那兒當(dāng)工程師
及時承認(rèn)錯誤咕痛,不要覺得承認(rèn)錯誤會有損你架構(gòu)師的身份
做不到這點(diǎn),公關(guān)行業(yè)比較適合你
不為了炫技而炫技
做不到這點(diǎn)喇嘱,你就是高中編程愛好者
精益求精
做不到這點(diǎn)茉贡,(我想了好久,但我還是不知道你適合去干什么者铜。)
什么樣的架構(gòu)叫好架構(gòu)腔丧?
代碼整齊放椰,分類明確,沒有common愉粤,沒有core
不用文檔砾医,或很少文檔,就能讓業(yè)務(wù)方上手
思路和方法要統(tǒng)一科汗,盡量不要多元
沒有橫向依賴藻烤,萬不得已不出現(xiàn)跨層訪問
對業(yè)務(wù)方該限制的地方有限制,該靈活的地方要給業(yè)務(wù)方創(chuàng)造靈活實(shí)現(xiàn)的條件
易測試头滔,易拓展
保持一定量的超前性
接口少怖亭,接口參數(shù)少
高性能
以上是我判斷一個架構(gòu)是不是好架構(gòu)的標(biāo)準(zhǔn),這是根據(jù)重要性來排列的坤检⌒诵桑客戶端架構(gòu)跟服務(wù)端架構(gòu)要考慮的問題和側(cè)重點(diǎn)是有一些區(qū)別的。下面我會針對每一點(diǎn)詳細(xì)講解一下:
代碼整齊早歇,分類明確倾芝,沒有common,沒有core
代碼整齊是每一個工程師的基本素質(zhì)箭跳,先不說你搞定這個問題的方案有多好晨另,解決速度有多快,如果代碼不整齊谱姓,一切都白搭借尿。因?yàn)槟愕拇a是要給別人看的,你自己也要看屉来。如果哪一天架構(gòu)有修改路翻,正好改到這個地方,你很容易自己都看不懂茄靠。另外茂契,破窗理論提醒我們,如果代碼不整齊分類不明確慨绳,整個架構(gòu)會隨著一次一次的拓展而越來越混亂掉冶。
分類明確的字面意思大家一定都了解,但還有一個另外的意思脐雪,那就是:不要讓一個類或者一個模塊做兩種不同的事情
厌小。如果有類或某模塊做了兩種不同的事情,一方面不適合未來拓展喂江,另一方面也會造成分類困難。
不要搞Common旁振,Core這些東西获询。每家公司的架構(gòu)代碼庫里面涨岁,最惡心的一定是這兩個名字命名的文件夾,我這么說一定不會錯吉嚣。不要開Common梢薪,Core這樣的文件夾,開了之后后來者一定會把這個地方搞得一團(tuán)糟尝哆,最終變成Common也不Common秉撇,Core也不Core。要記住秋泄,架構(gòu)是不斷成長的琐馆,是會不斷變化的。不是每次成長每次變化恒序,都是由你去實(shí)現(xiàn)的瘦麸。如果真有什么東西特別小,那就索性為了他單獨(dú)開辟一個模塊就好了歧胁,小就小點(diǎn)滋饲,關(guān)鍵是要有序。
不用文檔喊巍,或很少文檔屠缭,就能讓業(yè)務(wù)方上手
誰特么會去看文檔啊,業(yè)務(wù)方他們已經(jīng)被產(chǎn)品經(jīng)理逼得很忙了崭参。所以你要盡可能讓你的API名字可讀性強(qiáng)呵曹,對于iOS來說,objc這門語言的特性把這個做到了極致阵翎,函數(shù)名長就長一點(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òu)是不是有問題。 2. 要告知業(yè)務(wù)方要傳的東西是什么词疼,比如要傳Image俯树,那就寫上ofImage。如果要傳位置贰盗,那就要寫上IndexPath许饿,而不是用position這么籠統(tǒng)的東西 3. 沒有任何理由要把delegate作為參數(shù)傳進(jìn)去,一定不會有任何情況不得不這么做的舵盈。而且delegate這個參數(shù)根本不是這個函數(shù)要解決的問題的充要條件陋率,如果你發(fā)現(xiàn)你不得不這么做球化,那一定是架構(gòu)有問題!
思路和方法要統(tǒng)一瓦糟,盡量不要多元
解決一個問題會有很多種方案筒愚,但是一旦確定了一種方案,就不要在另一個地方采用別的方案了菩浙。也就是做架構(gòu)的時候巢掺,你得時刻記住當(dāng)初你決定要處理這樣類型的問題的方案是什么,以及你的初衷是什么劲蜻,不要搖擺不定陆淀。
另外,你當(dāng)初設(shè)立這個模塊一定是有想法有原因的斋竞,要記錄下你的解決思路倔约,不要到時候換個地方你又靈光一現(xiàn)啥的,引入了其他方案坝初,從而導(dǎo)致異構(gòu)浸剩。
要是一個框架里面解決同一種類似的問題有各種五花八門的方法或者類,我覺得做這個架構(gòu)的架構(gòu)師一定是自己都沒想清楚就開始搞了鳄袍。
沒有橫向依賴绢要,萬不得已不出現(xiàn)跨層訪問
沒有橫向依賴是很重要的,這決定了你將來要對這個架構(gòu)做修補(bǔ)所需要的成本有多大拗小。要做到?jīng)]有橫向依賴重罪,這是很考驗(yàn)架構(gòu)師的模塊分類能力和是否熟悉業(yè)務(wù)的。
跨層訪問是指數(shù)據(jù)流向了跟自己沒有對接關(guān)系的模塊哀九。有的時候跨層訪問是不可避免的剿配,比如網(wǎng)絡(luò)底層里面信號從2G變成了3G變成了4G,這是有可能需要跨層通知到View的阅束。但這種情況不多呼胚,一旦出現(xiàn)就要想盡一切辦法在本層搞定或者交給上層或者下層搞定,盡量不要出現(xiàn)跨層的情況息裸∮跨層訪問同樣也會增加耦合度,當(dāng)某一層需要整體替換的時候呼盆,牽涉面就會很大年扩。
對業(yè)務(wù)方該限制的地方有限制,該靈活的地方要給業(yè)務(wù)方創(chuàng)造靈活實(shí)現(xiàn)的條件
把這點(diǎn)做好访圃,很依賴于架構(gòu)師的經(jīng)驗(yàn)厨幻。架構(gòu)師必須要有能力區(qū)分哪些情況需要限制靈活性,哪些情況需要創(chuàng)造靈活性。比如對于Core Data技術(shù)棧來說况脆,ManagedObject理論上是可以出現(xiàn)在任何地方的平绩,那就意味著任何地方都可以修改ManagedObject,這就導(dǎo)致ManagedObjectContext在同步修改的時候把各種不同來源的修改同步進(jìn)去漠另。這時候就需要限制靈活性,只對外公開一個修改接口跃赚,不暴露任何ManagedObject在外面笆搓。
如果是設(shè)計一個ABTest相關(guān)的API的時候,我們又希望增加它的靈活性纬傲。使得業(yè)務(wù)方不光可以通過Target-Action的模式實(shí)現(xiàn)ABtest满败,也要可以通過Block的方式實(shí)現(xiàn)ABTest,要盡可能滿足靈活性叹括,減少業(yè)務(wù)方的使用成本算墨。
易測試易拓展
老生常談,要實(shí)現(xiàn)易測試易拓展汁雷,那就要提高模塊化程度净嘀,盡可能減少依賴關(guān)系,便于mock侠讯。另外挖藏,如果是高度模塊化的架構(gòu),拓展起來將會是一件非常容易的事情厢漩。
保持一定量的超前性
這一點(diǎn)能看出架構(gòu)師是否關(guān)注行業(yè)動態(tài)膜眠,是否能準(zhǔn)確把握技術(shù)走向。保持適度的技術(shù)上的超前性溜嗜,能夠使得你的架構(gòu)更新變得相對輕松宵膨。
另外,這里的超前性也不光是技術(shù)上的炸宵,還有產(chǎn)品上的辟躏。誰說架構(gòu)師就不需要跟產(chǎn)品經(jīng)理打交道了,沒事多跟產(chǎn)品經(jīng)理聊聊天焙压,聽聽他對產(chǎn)品未來走向的暢想鸿脓,你就可以在合理的地方為他的暢想留一條路子。同時涯曲,在創(chuàng)業(yè)公司的環(huán)境下野哭,很多產(chǎn)品需求其實(shí)只是為了趕產(chǎn)品進(jìn)度而產(chǎn)生的妥協(xié)方案,最后還是會轉(zhuǎn)到正軌的幻件。這時候業(yè)務(wù)方可以不實(shí)現(xiàn)轉(zhuǎn)到正規(guī)的方案拨黔,但是架構(gòu)這邊,是一定要為這種可預(yù)知的改變做準(zhǔn)備的绰沥。
接口少篱蝇,接口參數(shù)少
越少的接口越少的參數(shù)贺待,就能越降低業(yè)務(wù)方的使用成本。當(dāng)然零截,充要條件還是要滿足的麸塞,如何在滿足充要條件的情況下盡可能地減少接口和參數(shù)數(shù)量,這就能看出架構(gòu)師的功力有多深厚了涧衙。
高性能
為什么高性能排在最后一位哪工?
高性能非常重要,但是在客戶端架構(gòu)中弧哎,它不是第一考慮因素雁比。原因有下:
客戶端業(yè)務(wù)變化非常之快,做架構(gòu)時首要考慮因素應(yīng)當(dāng)是便于業(yè)務(wù)方快速滿足產(chǎn)品需求撤嫩,因此需要盡可能提供簡單易用效果好的接口給業(yè)務(wù)方偎捎,而不是提供高性能的接口給業(yè)務(wù)方。
蘋果平臺的性能非常之棒序攘,正常情況下很少會出現(xiàn)由于性能不夠?qū)е碌挠脩趔w驗(yàn)問題茴她。
蘋果平臺的優(yōu)化手段相對有限,甚至于有些時候即便動用了無所不用其極的手段乃至不擇手段犧牲了穩(wěn)定性程奠,性能提高很有可能也只不過是100ms到90ms的差距败京。10%的性能提升對于服務(wù)端來說很不錯了,因?yàn)榉?wù)端動不動就是幾十萬上百萬的訪問量梦染,幾十萬上百萬個10ms是很可觀的赡麦。但是對于客戶端的用戶來說,他無法感知這10ms的差別帕识,如果從10s優(yōu)化成9s用戶還是有一定感知的泛粹,但是100ms變90ms,我覺得吧肮疗,還是別折騰了晶姊。
但是!不重要不代表用不著去做伪货,關(guān)于性能優(yōu)化的東西们衙,我會對應(yīng)放到各系列文章里面去。比如網(wǎng)絡(luò)層優(yōu)化碱呼,那就會在網(wǎng)絡(luò)層方案的那篇文章里面去寫蒙挑,對應(yīng)每層架構(gòu)都有每層架構(gòu)的不同優(yōu)化方案,我都會在各自文章里面一一細(xì)說愚臀。
2015-4-2 11:28 補(bǔ): 關(guān)于架構(gòu)分層忆蚀?
昨晚上志豪看了這篇文章之后說,看到你這個題目本來我是期望看到關(guān)于架構(gòu)分層相關(guān)的東西的,但是你沒寫馋袜。
嗯男旗,確實(shí)沒寫,當(dāng)時沒寫的原因是感覺這個沒什么好寫的欣鳖。前面談?wù)摰郊軜?gòu)的方法的時候察皇,關(guān)于問題分類分模塊這一步時,架構(gòu)分層也屬于這一部分泽台,給我一筆帶過了让网。
既然志豪提出來了這個問題,我想可能大家關(guān)于這個也會有一些想法和問題师痕,那么我就在這兒講講吧。
其實(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ò)層锯茄、本地數(shù)據(jù)層厢塘。這里說三層
、四層
肌幽,跟TCP/IP所謂的五層或者七層不是同一種概念晚碾。再具體說就是:你這個架構(gòu)在邏輯上是幾層那就幾層,具體每一層叫什么喂急,做什么格嘁,沒有特定的規(guī)范。這主要是針對模塊分類而言的廊移。
也有說MVC架構(gòu)糕簿,MVVM架構(gòu)的,這種層次劃分狡孔,主要是針對數(shù)據(jù)流動的方向而言的懂诗。
在實(shí)際情況中,針對數(shù)據(jù)流動方向做的設(shè)計和針對模塊分類做的設(shè)計是會放在一起的苗膝,也就是說响禽,一個MVC架構(gòu)可以是四層:展現(xiàn)層、業(yè)務(wù)層、網(wǎng)絡(luò)層芋类、本地數(shù)據(jù)層隆嗅。
那么,為什么我要說這個侯繁?
大概在五六年前胖喳,業(yè)界很流行三層架構(gòu)
這個術(shù)語。然后各種文檔資料漫天的三層架構(gòu)
贮竟,并且喜歡把它與MVC
放在一起說丽焊,MVC三層架構(gòu)/三層架構(gòu)MVC
,以至于很多人就會認(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ù)流動方向偿短。
好欣孤,為什么流行起來的是三層架構(gòu)
,而不是四層架構(gòu)
或五層架構(gòu)
昔逗?
因?yàn)樗械哪K角色只會有三種:數(shù)據(jù)管理者
降传、數(shù)據(jù)加工者
、數(shù)據(jù)展示者
勾怒,意思也就是婆排,籠統(tǒng)說來,軟件只會有三層笔链,每一層扮演一個角色泽论。其他的第四層第五層,一般都是這三層里面的其中之一分出來的卡乾,最后都能歸納進(jìn)這三層的某一層中去翼悴,所以用三層架構(gòu)
來描述就比較普遍。
那么我們怎么做分層幔妨?
應(yīng)該如何做分層鹦赎,不是在做架構(gòu)的時候一開始就考慮的問題。雖然我們要按照自頂向下的設(shè)計方式來設(shè)計架構(gòu)误堡,但是一般情況下不適合直接從三層開始古话。一般都是先確定所有要解決的問題,先確定都有哪些模塊锁施,然后再基于這些模塊再往下細(xì)化設(shè)計陪踩。然后再把這些列出來的問題和模塊做好分類杖们。分類之后不出意外大多數(shù)都是三層。如果發(fā)現(xiàn)某一層特別龐大肩狂,那就可以再拆開來變成四層摘完,變成五層。
舉個例子:你要設(shè)計一個即時通訊的服務(wù)端架構(gòu)傻谁,怎么分層钳榨?
記住胞谈,不要一上來就把三層架構(gòu)
的規(guī)范套上去螟左,這樣做是做不出好架構(gòu)的卢未。
你要先確定都需要解決哪些問題。這里只是舉例子态蒂,我隨意列出一點(diǎn)意思意思就好了:
要解決用戶登錄杭措、退出的問題
解決不同用戶間數(shù)據(jù)交流的問題
解決用戶數(shù)據(jù)存儲的問題
如果是多臺服務(wù)器的集群,就要解決用戶連接的尋址問題
解決第一個問題需要一個鏈接管理模塊钾恢,鏈接管理模塊一般是通過鏈接池來實(shí)現(xiàn)手素。 解決第二個問題需要有一個數(shù)據(jù)交換模塊,從A接收來的數(shù)據(jù)要給到B赘那,這個事情由這個模塊來做。 解決第三個問題需要有個數(shù)據(jù)庫氯质,如果是服務(wù)于大量用戶募舟,那么就需要一個緩沖區(qū),只有當(dāng)需要存儲的數(shù)據(jù)達(dá)到一定量時才執(zhí)行寫操作闻察。 解決第四個問題可以有幾種解決方案拱礁,一個是集群中有那么幾臺服務(wù)器作為尋路服務(wù)器,所有尋路的服務(wù)交給那幾臺去做辕漂,那么你需要開發(fā)一個尋路服務(wù)的Daemon呢灶《む冢或者用廣播方式尋路,但如果尋路頻次非常高跋涣,會造成集群內(nèi)部網(wǎng)絡(luò)負(fù)載特別大。這是你要權(quán)衡的地方陈辱,目前流行的思路是去中心化,那么要解決網(wǎng)絡(luò)負(fù)載的問題沛贪,你就可以考慮配置一個緩存震贵。
于是我們有了這些模塊:
鏈接管理水评、數(shù)據(jù)交換、數(shù)據(jù)庫及其配套模塊之碗、尋路模塊
做到這里還遠(yuǎn)遠(yuǎn)沒有結(jié)束,你要繼續(xù)針對這四個模塊繼續(xù)往下細(xì)分褪那,直到足夠小為止幽纷。但是這里只是舉例子,所以就不往下深究了博敬。
另外友浸,我要提醒你的是,直到這時偏窝,還是跟幾層架構(gòu)毫無關(guān)系的收恢。當(dāng)你把所有模塊都找出來之后,就要開始整理你的這些模塊祭往,很有可能架構(gòu)圖就是這樣:
鏈接管理 收發(fā)數(shù)據(jù) 收發(fā)數(shù)據(jù) 數(shù)據(jù)交換 / \ \ 鏈接管理 數(shù)據(jù)交換 尋路服務(wù) ========\ / \ ========/ 數(shù)據(jù)庫服務(wù) 尋路服務(wù) 數(shù)據(jù)庫服務(wù) /
然后這些模塊分完之后你看一下圖伦意,嗯,1硼补、2驮肉、3,一共三層已骇,所以那就是三層架構(gòu)
啦离钝。在這里最消耗腦力最考驗(yàn)架構(gòu)師功力的地方就在于:找到所有需要的模塊
, 把模塊放在該放的地方
這個例子側(cè)重點(diǎn)在于如何分層,性能優(yōu)化褪储、數(shù)據(jù)交互規(guī)范和包協(xié)議卵渴、數(shù)據(jù)采集等其他一系列必要的東西都沒有放進(jìn)去,但看到這里鲤竹,相信你應(yīng)該了解架構(gòu)師是怎么對待分層問題的了吧浪读?
對的,答案就是沒有分層辛藻。所謂的分層都是出架構(gòu)圖之后的事情了瑟啃。所以你看別的架構(gòu)師在演講的時候,上來第一句話差不多都是:"這個架構(gòu)分為以下幾層..."揩尸。但考慮分層的問題的時機(jī)絕對不是一開始就考慮的蛹屿。另外,模塊一定要把它設(shè)計得獨(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文件夾检号?
評論區(qū)MatrixHero提到一點(diǎn):
關(guān)于common文件夾的問題齐苛,僅僅是文件夾而已凹蜂,別無他意藐俺。如果后期維護(hù)出了代碼混亂可能是因?yàn)橛郏头?wù)器溝通協(xié)議不統(tǒng)一吟吝,或代碼review不及時剑逃。應(yīng)該有專人維護(hù)公共類。
這是針對我前面提出的不要Common粟瞬,不要Core
而言的裙品,為什么我建議大家不要開Common文件夾?我打算分幾種情況給大家解釋一下市怎。
一般情況下区匠,我們都會有一些屬于這個項目的公共類驰弄,比如取定位坐標(biāo),比如圖像處理什乙。這些模塊可能非常小臣镣,就h和m兩個文件忆某。單獨(dú)拎出來成為一個模塊感覺不夠格阔蛉,但是又不屬于其他任何一個模塊状原。于是大家很有可能就會把它們放入Common里面,我目前見到的大多數(shù)工程和大多數(shù)文檔里面的代碼都喜歡這么做削锰。在當(dāng)時來看器贩,這么做看不出什么問題朋截,但關(guān)鍵在于:軟件是有生命部服,會成長的
。當(dāng)時分出來的小模塊奉芦,很有可能會隨著業(yè)務(wù)的成長,逐漸發(fā)展成大模塊昌讲,發(fā)展成大模塊后减噪,可以再把它從Common移出來單獨(dú)成立一個模塊筹裕。這個在理論上是沒有任何問題的,然而在實(shí)際操作過程中证逻,工程師在拓張這個小模塊的時候囚企,不太容易會去考慮橫向依賴
的問題龙宏,因?yàn)楫?dāng)時這些模塊都在Common里面伤疙,直接進(jìn)行互相依賴是非常符合直覺的徒像,而且也不算是不遵守規(guī)范锯蛀。然而要注意的是灭衷,這才是Commom代碼混亂
的罪魁禍?zhǔn)捉癫迹珻ommon文件夾縱容了不精心管理依賴
的做法。當(dāng)Common里面的模塊依賴關(guān)系變得復(fù)雜侵蒙,再想要移出來單獨(dú)成立一個模塊纷闺,就不是當(dāng)初設(shè)置Common時想的等規(guī)模大了再移除也不遲
那么簡單了份蝴。
另外婚夫,Common
有的時候也不僅僅是一個文件夾案糙。
在使用Cocoapods來管理項目庫的時候时捌,Common
往往就是一個pod奢讨。這個pod里面會有A/B/C/D/E這些函數(shù)集或小模塊拿诸。如果要新開一個app或者Demo佳镜,勢必會使用到Common這個pod,這么做蟀伸,往往會把不需要包含的代碼也包含進(jìn)去缅刽,我對項目有高度潔癖衰猛,這種情況會讓我覺得非常不舒服啡省。
舉個例子:早年安居客的app還不是集齊所有新房
卦睹、二手房
、租房
業(yè)務(wù)的障斋。當(dāng)你剛開始寫新房
這個app的時候邀层,創(chuàng)建了一個Common這個pod遂庄,這里面包含了一些對于新房
來說比較Common的代碼,也包含了對于這個app來說比較Common的代碼只磷。過了半年或者一年,你要開始二手房
這個app钮追,我覺得大多數(shù)人都會選擇讓二手房
也包含這個Common元媚,于是這個Common很有可能自己走上另一條發(fā)展的道路刊棕。等到了租房
這個業(yè)務(wù)要開app的時候待逞,Common已經(jīng)非常之龐大,相信這時候的你也不會去想整理Common的事情了嗤无,先把租房
搞定当犯,于是Common最終就變成了一坨屎嚎卫。
就對于上面的例子來說宏榕,還有一個要考慮的是麻昼,分出來的三個業(yè)務(wù)很有可能會有三個Common涌献,假設(shè)三個Common里面都有公共的功能首有,交給了三個團(tuán)隊去打理,如果遇到某個子模塊需要升級您旁,那么三個Common里面的這個子模塊都要去同步升級鹤盒,這是個很不效率的事情侦副。另外秦驯,很有可能三個Common到最后發(fā)展成彼此不兼容译隘,但是代碼相似度非常之高,這個在架構(gòu)上题篷,是屬于分類條理不清
番枚。
就在去年年中的時候户辫,安居客決定將三個業(yè)務(wù)歸并到同一個App渔欢。好了瘟忱,如果你是架構(gòu)師访诱,面對這三個Common,你打算怎么辦九榔?要想最快出成果剩蟀,那就只好忍受代碼冗余,趕緊先把架子搭起來再說育特,否則你面對的就是剪不斷理還亂的Common先朦。此時Common就已經(jīng)很無奈地變成一坨屎了喳魏。這樣的Common,你自己說不定也搞不清楚它里面到底都有些什么了涮拗,交給任何一個人去打理三热,他都不敢做徹底的整理的就漾。
還有就是抑堡,Common本身就是一個粒度非常大的模塊首妖。在阿里這樣大規(guī)模的團(tuán)隊中爷恳,即便新開一個業(yè)務(wù)棚壁,都需要在整個app的環(huán)境下開發(fā)栈虚,為什么魂务?因?yàn)槟K拆分粒度不夠,要想開一個新業(yè)務(wù)熔酷,必須把其他業(yè)務(wù)的代碼以及依賴全部拉下來纯陨,然后再開新入口留储,你的新業(yè)務(wù)才能進(jìn)行正常的代碼編寫和調(diào)試获讳。然而你的新業(yè)務(wù)其實(shí)只依賴首頁入口丐膝、網(wǎng)絡(luò)庫等這幾個小模塊帅矗,不需要依賴其他那么多的跟你沒關(guān)系的業(yè)務(wù)』氪耍現(xiàn)在每次打開天貓的項目凛俱,我都要等個兩三分鐘蒲犬,這非常之蛋疼原叮。
但是大家真的不知道這個原因嗎?知道了這個原因赡若,為什么沒人去把這些粒度不夠細(xì)的模塊整理好逾冬?在我看來,這件事沒人敢做匹厘。
原來大家用的好好的脐区,手段爛就爛一點(diǎn)牛隅,你改了你能保證不出錯?
這么復(fù)雜的東西匕累,短期之內(nèi)你肯定搞不好默伍,任務(wù)量和工時都不好估欢嘿,你leader會覺得你在騙工時玩自己的事情。
就算你搞定了也糊,QA這邊肯定再需要做一次全面的回歸測試炼蹦,任務(wù)量極大,難以說服他們配合你的工作狸剃。
花這么大的成本只是為了減少開啟項目時候等待IDE打開時的那幾分鐘時間掐隐?我想如果我是你leader,我也應(yīng)該不會批準(zhǔn)你做這樣的事情的捕捂。所以瑟枫,與其到了后面吃這個苦頭,不如一開始做架構(gòu)的時候就不要設(shè)置Common膝擂,到后面就能省力很多全闷。架構(gòu)師的工作為什么是功在當(dāng)代利在千秋勘纯,架構(gòu)師的素質(zhì)為什么對團(tuán)隊這么重要?我覺得這里就是一個最好的體現(xiàn)。
簡而言之,不建議開Common的原因如下:
Common不僅僅是一個文件夾来庭,它也會是一個Pod科盛。不管是什么恍飘,在Common里面很容易形成錯綜復(fù)雜的小模塊依賴,在模塊成長過程中,會縱容工程師不注意依賴的管理,乃至于將來如果要將模塊拆分出去,會非常的困難刷后。
Common本身與細(xì)粒度模塊設(shè)計的思想背道而馳,屬于一種不合適的偷懶手段,在將來業(yè)務(wù)拓張會成為阻礙逊桦。
一旦設(shè)置了Common寺渗,就等于給地獄之門打開了一個小縫,每次業(yè)務(wù)迭代都會有一些不太好分類的東西放入Common,這就給維護(hù)Common的人帶來了非常大的工作量,而且這些工作量全都是體力活,非常容易出錯不铆。
那么劳坑,不設(shè)Common會帶來哪些好處框仔?
強(qiáng)迫工程師在業(yè)務(wù)拓張的時候?qū)⒁蕾嚬芾淼氖虑榭紤]進(jìn)去,讓模塊在一開始發(fā)展的時候就有自己的土壤,成長空間和靈活度非常大。
減少各業(yè)務(wù)模塊或者Demo的體積,不需要的模塊不會由于Common的存在而包含在內(nèi)仁锯。
可維護(hù)性大大提高双炕,模塊升級之后要做的同步工作非常輕松,解放了那個苦逼的Common維護(hù)者,更多的時間可以用在更實(shí)質(zhì)的開發(fā)工作上融求。
符合細(xì)粒度模塊劃分的架構(gòu)思想陷舅。
Common的好處只有一個耙箍,就是前期特別省事兒。然而它的壞處比好處要多太多。不設(shè)置Common敷搪,再小的模塊再小的代碼也單獨(dú)拎出來闸与,最多就是Podfile里面要多寫幾行木羹,多寫幾行最多只花費(fèi)幾分鐘。但若要消除Common所帶來的罪孽绝页,不是這幾分鐘就能搞定的事情牙咏。既然不用Common的好處這么多摔握,那何樂而不為呢奢浑?
假設(shè)將來你的項目中有一個類是用來做Location的,哪怕只有兩個文件聪富,也給他開一個模塊就叫Location涮雷。如果你的項目中有一個類是用來做ImageProcess的拾枣,那也開一個模塊就叫ImageProcess邑茄。不要都放到Common里面去,將來你再開新的項目或者新的業(yè)務(wù),用Location就寫依賴Location,用ImageProcess就寫依賴ImageProcess棺滞,不要再依賴Common了,這樣你的項目也好管理,管理Common的那個人日子過得也輕松(這個人其實(shí)都可以不需要了,把他的工資加到你頭上不是更好饲做?:D),將來要升級疆柔,顧慮也少。
結(jié)束
一下子挖了個大坑输吏,在開篇里扯了一些淡权旷。
嗯,干貨會在后續(xù)的系列文章里面撲面而來的贯溅!