架構(gòu)漫談(八):從架構(gòu)的角度看如何寫好代碼

在第六篇文章中县遣,我們得出一個結(jié)論糜颠,軟件架構(gòu)實際上包括了:代碼架構(gòu)汹族,以及承載代碼運行的硬件部署架構(gòu)。實際上其兴,硬件部署架構(gòu)最終還是由代碼的架構(gòu)來決定顶瞒。因為代碼架構(gòu)不合理,是無法把一個運行單元分拆出多個來的元旬,那么硬件架構(gòu)能分拆的就非常的有限榴徐,整個系統(tǒng)最終很難長的更大守问。
  所以我們經(jīng)常會聽說,重寫代碼坑资,推翻原有架構(gòu)耗帕,重新設(shè)計等等說法,來說明架構(gòu)的進化袱贮。這實際上就是當初為了完成任務(wù)仿便,沒有充分思考所帶來的后果。這也并不是架構(gòu)進化的事情攒巍,而是個人對問題領(lǐng)域的逐漸深入理解的過程嗽仪。所以有必要再討論一下,代碼的架構(gòu)應(yīng)該是怎樣的柒莉。
  本文會在之前幾篇文章的基礎(chǔ)上闻坚,進一步探討如何把架構(gòu)的思考進行落地,細化到我們代碼的實踐當中兢孝,盡量不要讓代碼成為系統(tǒng)長大的瓶頸窿凤,降低架構(gòu)分拆的成本。
  在前面我們提到西潘,軟件實際上是對現(xiàn)實生活的模擬卷玉,虛擬化。這是一個非常重要的前提喷市,直接決定了我們的代碼應(yīng)該分為幾部分相种。結(jié)合每個部署單元所承擔的責任,可以明確的拆分為兩個不同的責任:
表達業(yè)務(wù)邏輯的代碼品姓。很多人把這部分叫做Domain Logic寝并,或者叫Domain Model。這部分實際是來源于生活的腹备,必須保持和現(xiàn)實生活中的切分一致衬潦,并非人為的抽象而成。
對用戶提供訪問并保存業(yè)務(wù)邏輯運行結(jié)果的代碼植酥。計算機的狀態(tài)保存有一個缺陷镀岛,本機保留業(yè)務(wù)運行結(jié)果有很大的問題,一般都在外存儲設(shè)備上保存友驮,也便于擴展漂羊。
  所以單個部署單元的代碼可以分為兩個部分,如下圖所示:


24634-20160406174742578-150824882.png

  從這個圖中可以看出卸留,軟件代碼的相關(guān)利益人為運行時的訪問人員和存儲設(shè)備走越。而service的代碼是最復雜的,需要服務(wù)于三方耻瑟,代碼人員的負擔是最重的旨指。為了把這三方的變化對service的影響降到最低赏酥,對于service還必須進一步的分拆為三個部分,讓每一個部分都能夠獨立的變化谆构,這樣這三方的變化就不會產(chǎn)生連鎖響應(yīng)裸扶,降低成本。如下圖所示:

24634-20160406174742125-1292953920.png

  這樣搬素,就劃分成了幾個責任:
Service就專注于user的需求姓言,并組合Glue Code提供的服務(wù)完成需求。
Glue Code專注于組合business的調(diào)用蔗蹋,管理Business里面對象的生命周期何荚,并且通過Repository保存或加載Business的狀態(tài)
Business專注于實現(xiàn)業(yè)務(wù)的核心模型。
Repository專注于數(shù)據(jù)的保存猪杭,并和存儲設(shè)備一一對應(yīng)餐塘。
  大家注意看,還是樹形架構(gòu)皂吮。并且左側(cè)的主要需要計算機的相關(guān)理論知識戒傻,并且要直接面對用戶的需求。右側(cè)的更多的需要面對業(yè)務(wù)的核心蜂筹。只要這幾塊的開發(fā)人員互相商量好了接口定義需纳,這幾個部分的開發(fā)就可以并行的進行,極大的提升開發(fā)的效率艺挪,縮短開發(fā)的時間不翩。要做好這幾部分,還需要注意麻裳,邏輯只允許存在于Business中口蝠,Service、Glue Code津坑、Repository都不允許存在業(yè)務(wù)邏輯妙蔗。為什么呢?首先我們來看看什么叫業(yè)務(wù)邏輯疆瑰。
  什么叫業(yè)務(wù)邏輯眉反?
  首先這個定義的前提是指軟件代碼中的邏輯,不是現(xiàn)實生活中的邏輯穆役。在軟件代碼中寸五,不需縮進和計算的順序調(diào)用,包括縮進的代碼目的是catch exception的孵睬,都不算邏輯播歼,除此以外都是邏輯伶跷。以下用嚴格的順序調(diào)用來指代這種代碼掰读。因為順序調(diào)用是計算機的特性秘狞,由編譯器來決定的,當然最本質(zhì)的是因為我們計算的基礎(chǔ)都是圖靈機蹈集。在現(xiàn)實生活中烁试,順序調(diào)用也是邏輯,大家不要和我們這里說的業(yè)務(wù)邏輯相混淆拢肆。
  為什么說除了Business代碼中有邏輯以外减响,其他地方不能有邏輯呢? 我們每個部分分別分析:
如果service里面不是嚴格的順序調(diào)用郭怪,有很多分支支示,那么說明這個service做了兩件或者兩件以上的事情。必須把這個service分拆鄙才,確保每個service只做一件事情颂鸿。因為如果不這么分拆的話,一旦這個service中的某各部分發(fā)生變動攒庵,其他的部分的執(zhí)行必定會受影響嘴纺。而確定到底有哪些影響的溝通成本非常高,其他相關(guān)利益方?jīng)]有動力去配合浓冒,我們往往不會投入精力仔細評估栽渴。最后上線會出很多不可預料的問題,最終會導致?lián)p失用戶的利益稳懒,并且肯定會導致返工闲擦,損壞自己的利益。如果是有計算的邏輯的話场梆,比如受益計算佛致,訂單金額計算等,那么這部分應(yīng)該是Business代碼需要完成的辙谜,不能交給service代碼來實現(xiàn)俺榆。
Glue Code里面如果不是嚴格的順序調(diào)用,同理會和service一樣遇到同樣的問題装哆。
Repository里面如果不是嚴格的順序調(diào)用罐脊,包括存儲訪問的代碼里面(比如SQL),會導致邏輯進入到存儲設(shè)備中蜕琴。存儲設(shè)備的主要目的是拿來存儲的萍桌,一旦變成了邏輯計算的主體,就會導致存儲設(shè)備無法通過增加機器的方式橫向擴展長大凌简。這個時候就沒有架構(gòu)了上炎,只能換性能更好的機器,這個叫scale up。只有scale out才能算架構(gòu)藕施。
  以上都會導致架構(gòu)無法快速的橫向擴展和分拆寇损,并且增加了修改的成本,這些是不符合開發(fā)人員以及業(yè)務(wù)的利益的裳食。
  這么做的好處有哪些呢矛市?
Service、Glue Code诲祸、Repository里面的代碼是嚴格的順序調(diào)用浊吏,那么這些代碼只要做連通性測試即可,不需要單元測試救氯。因為這些代碼都需要和很多上下文打交道找田,很難做單元測試。這樣才算是真正的組合着憨。
Business不訪問任何上下文午阵,不訪問任何具體的設(shè)備,所以這部分代碼是非常容易寫單元測試的享扔,并且單元測試必須100%覆蓋底桂。因為其他地方?jīng)]有業(yè)務(wù)邏輯,所以一旦有問題惧眠,就可以斷定是Model的問題籽懦,單元測試肯定可以發(fā)現(xiàn)。如果單元測試沒有發(fā)現(xiàn)問題氛魁,那么單元測試一定有問題暮顺。線上問題的模擬也就變得非常的簡單,單元測試也能夠得到進一步的補充秀存。
Repository很容易按照存儲設(shè)備本身的最小訪問粒度來完成工作捶码,比如DB,完全可以做到單表訪問或链。因為這個時候存儲設(shè)備只關(guān)心存取數(shù)據(jù)惫恼,完全和業(yè)務(wù)沒有關(guān)系。做表的分拆也是非常容易的事情澳盐,存儲設(shè)備通過增加機器就可以橫向擴展長大祈纯。很多人會擔心說,沒有了join叼耙,訪問DB的次數(shù)是不是更多了腕窥,會導致性能下降? 按照現(xiàn)在網(wǎng)絡(luò)的條件筛婉,網(wǎng)絡(luò)訪問和Disk IO訪問的差距已經(jīng)不大了簇爆,合理的設(shè)計下,多訪問幾次DB并不會導致這個問題。另外如果多臺DB的話入蛆,還能通過并行加速訪問响蓉。
由于Service、Glue Code安寺、Repository代碼簡單了,才可以讓我們的開發(fā)人員投入更多的時間研究業(yè)務(wù)首尼,畢竟這部分才是軟件所真正服務(wù)的對象挑庶。
  我們再來看一個實際的例子,如下圖所示:

24634-20160406174743125-1907685125.jpg

  Manager類實際就是Glue Code软能。有幾個注意點需要說明一下:
不能把Business Model當做數(shù)據(jù)對象來處理迎捺,Model關(guān)心的實際上是業(yè)務(wù)行為,數(shù)據(jù)只是是這些行為的結(jié)果查排。所以Glue Code需要把Model轉(zhuǎn)換為Entity凳枝,Entity和存儲設(shè)備里面的存儲粒度一一對應(yīng)。比如在DB中跋核,每個Entity對應(yīng)一張表岖瑰,并且跟著表的變化而變化,這樣就保證存儲的變更不會影響Model砂代。同樣Service和用戶之間的數(shù)據(jù)交互蹋订,也是不會和Model之間相關(guān)的,確保用戶的需求變化刻伊,不會影響到Model露戒。因為用戶的需求變化是最頻繁的,沒有邏輯捶箱,可以讓我快速的滿足業(yè)務(wù)的需求智什。
在Service這里,最好不要考慮代碼重用丁屎。因為當多個不同的角色訪問同一個接口荠锭,一旦某個角色的需求發(fā)生了變化,就會要求開發(fā)人員去修改晨川。而這個修改往往會影響到其他的角色节沦,需要這些角色一起配合來確定是否受影響,但是這些角色因為沒有需求础爬,往往不會配合甫贯。這樣就給開發(fā)人員造成了很多不必要的溝通,成本是非常高的看蚜。最終都會導致線上Bug叫搁,影響最終的用戶。所以盡量給不同的角色不同的Service,避免重用渴逻,降低溝通成本疾党。很多人會說這樣Service不就太多了嗎? 這樣Service注冊惨奕,查找等管理需求就出現(xiàn)了雪位,Service治理中心就是來解決這個問題的。因為Service里面沒有邏輯梨撞,所以開發(fā)和管理非常的簡單雹洗,可以快速應(yīng)對業(yè)務(wù)的變化。我們只有更快地變卧波,更容易的變时肿,才能更好地應(yīng)對變。
Business Model是必須要重用的港粱,一旦發(fā)現(xiàn)重用出現(xiàn)問題螃成,那么說明Business Model的識別出現(xiàn)了問題,這是一個我們要重新思考Model的信號查坪。Business Model必須是一個完美的樹狀寸宏,如果不是,也說明Model的識別出了問題偿曙。
  在實際操作中击吱,Service、Glue Code遥昧、Repository不能有邏輯覆醇,實際上和很多人的觀念是沖突的,認為這個根本做不到炭臭。做到這一點需要很多的學習成本永脓,但是一定可以做得到。當發(fā)現(xiàn)做不到的時候鞋仍,可以斷定是業(yè)務(wù)的分析出了問題常摧。比如不該合并的合并了,不該計算的計算了威创。這個問題一定有辦法解決的落午,做不到都是理由,無非是想早點把自己的工作結(jié)束罷了肚豺。雖然剛開始會比較困難溃斋,一旦把這個觀念變成自覺,開發(fā)的質(zhì)量和效率馬上就能高好幾個級別吸申。
  我的游泳教練曾和我說過這些話梗劫,我至今記憶猶新:“業(yè)余選手享甸,越想從水里浮起來,就越想把頭抬起來梳侨,身體反而沉下去蛉威。只有克服恐懼,把頭往水里壓下去走哺,身體才能夠從水里浮起來蚯嫌。真正專業(yè)的習慣往往是和我們?nèi)粘5男袨橄喾吹摹薄?br>   我們真正想快速的完成代碼工作,就要克服自己對時間的恐懼丙躏,真正的去研究業(yè)務(wù)的問題择示,相關(guān)stakeholder的利益,把這個變成我們的習慣彼哼。寫代碼的時候讓該出現(xiàn)邏輯的地方出現(xiàn)邏輯对妄,讓不該出現(xiàn)的地方不能出現(xiàn)湘今。一旦不該出現(xiàn)的地方出現(xiàn)了邏輯敢朱,那么要馬上意識到,這個地方是一個坑摩瞎,這個問題一定和業(yè)務(wù)的分析不透徹有關(guān)系拴签。
  很多人可能會把這個做法和Martin Fowler曾經(jīng)提出過充血模型和貧血模型來比較,和Domain Driven Design來比較旗们,其實沒有必要蚓哩。這個分拆完全是從軟件所解決的問題,根據(jù)軟件架構(gòu)推導出來的上渴,很多地方和兩位前輩的觀點是一致的岸梨,但是并不完全等同。
  以上只是針對單一的Service部署單元的分析稠氮,擴展開去曹阔,對于其他的部署單元也是類似的。每個單元的下一級都可以認為是Repository隔披,每個單元的上一級都可以認為是User赃份。這些實踐在我自己的項目中都有用到,非常的有效奢米,迭代的速度非常的快抓韩。很多人擔心Business Model建不好,其實沒關(guān)系鬓长,剛開始可以粗糙一點谒拴,后續(xù)可以慢慢的完善。這個架構(gòu)已經(jīng)隔離好了每個部分的變化對其他部分的影響涉波,變化成本都在可控的范圍之內(nèi)彪薛。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末茂装,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子善延,更是在濱河造成了極大的恐慌少态,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件易遣,死亡現(xiàn)場離奇詭異彼妻,居然都是意外死亡,警方通過查閱死者的電腦和手機豆茫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進店門侨歉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人揩魂,你說我怎么就攤上這事幽邓。” “怎么了火脉?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵牵舵,是天一觀的道長。 經(jīng)常有香客問我倦挂,道長畸颅,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任方援,我火速辦了婚禮没炒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘犯戏。我一直安慰自己送火,他們只是感情好,可當我...
    茶點故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布先匪。 她就那樣靜靜地躺著种吸,像睡著了一般。 火紅的嫁衣襯著肌膚如雪胚鸯。 梳的紋絲不亂的頭發(fā)上骨稿,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天,我揣著相機與錄音姜钳,去河邊找鬼坦冠。 笑死,一個胖子當著我的面吹牛哥桥,可吹牛的內(nèi)容都是我干的辙浑。 我是一名探鬼主播,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼拟糕,長吁一口氣:“原來是場噩夢啊……” “哼判呕!你這毒婦竟也來了倦踢?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤侠草,失蹤者是張志新(化名)和其女友劉穎辱挥,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體边涕,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡晤碘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了功蜓。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片园爷。...
    茶點故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖式撼,靈堂內(nèi)的尸體忽然破棺而出童社,到底是詐尸還是另有隱情,我是刑警寧澤著隆,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布扰楼,位于F島的核電站,受9級特大地震影響旅东,放射性物質(zhì)發(fā)生泄漏灭抑。R本人自食惡果不足惜十艾,卻給世界環(huán)境...
    茶點故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一抵代、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧忘嫉,春花似錦荤牍、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至访递,卻和暖如春晦嵌,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背拷姿。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工惭载, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人响巢。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓描滔,卻偏偏與公主長得像,于是被迫代替她去往敵國和親踪古。 傳聞我的和親對象是個殘疾皇子含长,可洞房花燭夜當晚...
    茶點故事閱讀 45,685評論 2 360

推薦閱讀更多精彩內(nèi)容