【轉(zhuǎn)】架構(gòu)漫談(八):從架構(gòu)的角度看如何寫好代碼

原文鏈接
本文首發(fā)于 InfoQ 旗下垂直社群聊聊架構(gòu)(微信號 archtime)。

架構(gòu)漫談是由資深架構(gòu)師王概凱 Kevin 執(zhí)筆的系列專欄,專欄將會以 Kevin 的架構(gòu)經(jīng)驗為基礎(chǔ),逐步討論什么是架構(gòu)、怎樣做好架構(gòu)、軟件架構(gòu)如何落地、如何寫好程序等問題。

本文是漫談架構(gòu)專欄的第八篇,作者 Kevin 舉例介紹了如何寫好代碼。當(dāng)我們有了好的架構(gòu),那就需要考慮如何將架構(gòu)落地,而這個時候,代碼就顯得無比重要了!千萬不要讓代碼成為架構(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)的進化。這實際上就是當(dāng)初為了完成任務(wù),沒有充分思考所帶來的后果。這也并不是架構(gòu)進化的事情,而是個人對問題領(lǐng)域的逐漸深入理解的過程。所以有必要再討論一下,代碼的架構(gòu)應(yīng)該是怎樣的。

本文會在之前幾篇文章的基礎(chǔ)上,進一步探討如何把架構(gòu)的思考進行落地,細化到我們代碼的實踐當(dāng)中,盡量不要讓代碼成為系統(tǒng)長大的瓶頸,降低架構(gòu)分拆的成本。

在前面我們提到,軟件實際上是對現(xiàn)實生活的模擬,虛擬化。這是一個非常重要的前提,直接決定了我們的代碼應(yīng)該分為幾部分。結(jié)合每個部署單元所承擔(dān)的責(zé)任,可以明確的拆分為兩個不同的責(zé)任:

????1、表達業(yè)務(wù)邏輯的代碼。很多人把這部分叫做 Domain Logic,或者叫 Domain Model。這部分實際是來源于生活的,必須保持和現(xiàn)實生活中的切分一致,并非人為的抽象而成。

????2、對用戶提供訪問并保存業(yè)務(wù)邏輯運行結(jié)果的代碼。計算機的狀態(tài)保存有一個缺陷,本機保留業(yè)務(wù)運行結(jié)果有很大的問題,一般都在外存儲設(shè)備上保存,也便于擴展。

所以單個部署單元的代碼可以分為兩個部分,如下圖所示:

a021b8987b08db5e11d754cb7bc05a11.png

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


43f0c72a632e5b40037f57d31d278922.png

這樣,就劃分成了幾個責(zé)任:

????1、Service 就專注于 user 的需求,并組合 Glue Code 提供的服務(wù)完成需求。
????2、Glue Code 專注于組合 business 的調(diào)用,管理 Business 里面對象的生命周期,并且通過 Repository 保存或加載 Business 的狀態(tài)
????3、Business 專注于實現(xiàn)業(yè)務(wù)的核心模型。
????4、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)用是計算機的特性,由編譯器來決定的,當(dāng)然最本質(zhì)的是因為我們計算的基礎(chǔ)都是圖靈機。在現(xiàn)實生活中,順序調(diào)用也是邏輯,大家不要和我們這里說的業(yè)務(wù)邏輯相混淆。

為什么說除了 Business 代碼中有邏輯以外,其他地方不能有邏輯呢? 我們每個部分分別分析:

????1、如果 service 里面不是嚴格的順序調(diào)用,有很多分支,那么說明這個 service 做了兩件或者兩件以上的事情。必須把這個 service 分拆,確保每個 service 只做一件事情。因為如果不這么分拆的話,一旦這個 service 中的某各部分發(fā)生變動,其他的部分的執(zhí)行必定會受影響。而確定到底有哪些影響的溝通成本非常高,其他相關(guān)利益方?jīng)]有動力去配合,我們往往不會投入精力仔細評估。最后上線會出很多不可預(yù)料的問題,最終會導(dǎo)致?lián)p失用戶的利益,并且肯定會導(dǎo)致返工,損壞自己的利益。如果是有計算的邏輯的話,比如受益計算,訂單金額計算等,那么這部分應(yīng)該是 Business 代碼需要完成的,不能交給 service 代碼來實現(xiàn)。
????2、Glue Code 里面如果不是嚴格的順序調(diào)用,同理會和 service 一樣遇到同樣的問題。
????3、Repository 里面如果不是嚴格的順序調(diào)用,包括存儲訪問的代碼里面(比如 SQL),會導(dǎo)致邏輯進入到存儲設(shè)備中。存儲設(shè)備的主要目的是拿來存儲的,一旦變成了邏輯計算的主體,就會導(dǎo)致存儲設(shè)備無法通過增加機器的方式橫向擴展長大。這個時候就沒有架構(gòu)了,只能換性能更好的機器,這個叫 scale up。只有 scale out 才能算架構(gòu)。

以上都會導(dǎo)致架構(gòu)無法快速的橫向擴展和分拆,并且增加了修改的成本,這些是不符合開發(fā)人員以及業(yè)務(wù)的利益的。

這么做的好處有哪些呢?

????1、Service、Glue Code、Repository 里面的代碼是嚴格的順序調(diào)用,那么這些代碼只要做連通性測試即可,不需要單元測試。因為這些代碼都需要和很多上下文打交道,很難做單元測試。這樣才算是真正的組合。

????2、Business 不訪問任何上下文,不訪問任何具體的設(shè)備,所以這部分代碼是非常容易些單元測試的,并且單元測試必須 100% 覆蓋。因為其他地方?jīng)]有業(yè)務(wù)邏輯,所以一旦有問題,就可以斷定是 Model 的問題,單元測試肯定可以發(fā)現(xiàn)。如果單元測試沒有發(fā)現(xiàn)問題,那么單元測試一定有問題。線上問題的模擬也就變得非常的簡單,單元測試也能夠得到進一步的補充。

????3、Repository 很容易按照存儲設(shè)備本身的最小訪問粒度來完成工作,比如 DB,完全可以做到單表訪問。因為這個時候存儲設(shè)備只關(guān)心存取數(shù)據(jù),完全和業(yè)務(wù)沒有關(guān)系。做表的分拆也是非常容易的事情,存儲設(shè)備通過增加機器就可以橫向擴展長大。很多人會擔(dān)心說,沒有了 join,訪問 DB 的次數(shù)是不是更多了,會導(dǎo)致性能下降? 按照現(xiàn)在網(wǎng)絡(luò)的條件,網(wǎng)絡(luò)訪問和 Disk IO 訪問的差距已經(jīng)不大了,合理的設(shè)計下,多訪問幾次 DB 并不會導(dǎo)致這個問題。另外如果多臺 DB 的話,還能通過并行加速訪問。

????4、由于 Service、Glue Code、Repository 代碼簡單了,才可以讓我們的開發(fā)人員投入更多的時間研究業(yè)務(wù),畢竟這部分才是軟件所真正服務(wù)的對象。

我們再來看一個實際的例子,如下圖所示:


d3946c635cc0b3fe3d66a6a87b2d4c20.jpg

Manager 類實際就是 Glue Code。有幾個注意點需要說明一下:

????1、不能把 Business Model 當(dāng)做數(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ù)的需求。

????2、在 Service 這里,最好不要考慮代碼重用。因為當(dāng)多個不同的角色訪問同一個接口,一旦某個角色的需求發(fā)生了變化,就會要求開發(fā)人員去修改。而這個修改往往會影響到其他的角色,需要這些角色一起配合來確定是否受影響,但是這些角色因為沒有需求,往往不會配合。這樣就給開發(fā)人員造成了很多不必要的溝通,成本是非常高的。最終都會導(dǎo)致線上 Bug,影響最終的用戶。所以盡量給不同的角色不同的 Service,避免重用,降低溝通成本。很多人會說這樣 Service 不就太多了嗎? 這樣 Service 注冊,查找等管理需求就出現(xiàn)了,Service 治理中心就是來解決這個問題的。因為 Service 里面沒有邏輯,所以開發(fā)和管理非常的簡單,可以快速應(yīng)對業(yè)務(wù)的變化。我們只有更快地變,更容易的變,才能更好地應(yīng)對變。

????3、Business Model 是必須要重用的,一旦發(fā)現(xiàn)重用出現(xiàn)問題,那么說明 Business Model 的識別出現(xiàn)了問題,這是一個我們要重新思考 Model 的信號。Business Model 必須是一個完美的樹狀,如果不是,也說明 Model 的識別出了問題。

????4、在實際操作中,Service、Glue Code、Repository 不能有邏輯,實際上和很多人的觀念是沖突的,認為這個根本做不到。做到這一點需要很多的學(xué)習(xí)成本,但是一定可以做得到。當(dāng)發(fā)現(xiàn)做不到的時候,可以斷定是業(yè)務(wù)的分析出了問題。比如不該合并的合并了,不該計算的計算了。這個問題一定有辦法解決的,做不到都是理由,無非是想早點把自己的工作結(jié)束罷了。雖然剛開始會比較困難,一旦把這個觀念變成自覺,開發(fā)的質(zhì)量和效率馬上就能高好幾個級別。

我的游泳教練曾和我說過這些話,我至今記憶猶新:“業(yè)余選手,越想從水里浮起來,就越想把頭抬起來,身體反而沉下去。只有克服恐懼,把頭往水里壓下去,身體才能夠從水里浮起來。真正專業(yè)的習(xí)慣往往是和我們?nèi)粘5男袨橄喾吹摹薄?/p>

我們真正想快速的完成代碼工作,就要克服自己對時間的恐懼,真正的去研究業(yè)務(wù)的問題,相關(guān) stakeholder 的利益,把這個變成我們的習(xí)慣。寫代碼的時候讓該出現(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)推導(dǎo)出來的,很多地方和兩位前輩的觀點是一致的,但是并不完全等同。

以上只是針對單一的 Service 部署單元的分析,擴展開去,對于其他的部署單元也是類似的。每個單元的下一級都可以認為是 Repository,每個單元的上一級都可以認為是 User。這些實踐在我自己的項目中都有用到,非常的有效,迭代的速度非常的快。很多人擔(dān)心 Business Model 建不好,其實沒關(guān)系,剛開始可以粗糙一點,后續(xù)可以慢慢的完善。這個架構(gòu)已經(jīng)隔離好了每個部分的變化對其他部分的影響,變化成本都在可控的范圍之內(nèi)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容