因爲本身身處SAAS行業,在經歷了幾輪複雜需求的蹂躪以後,我一直試圖尋找一種解法,能夠儘可能cover住複雜多變的需求。在過去的一年中,經過反覆閱讀和實踐,彷佛讓我對此有了一些清晰的思路,因此我想寫一點東西總結一下本身的這一年裏的思考。git
在咱們的項目初期,項目的規模可能比較小,代碼量不多,咱們的代碼或許還能整理的比較乾淨,就像這幾組交換機的網線同樣,比較有條理。數據庫
可是隨着功能複雜以後,項目也隨之變得龐大,整個代碼就可能會和這個機房同樣,很是的混亂。在經歷幾回這種情況以後,因而我便在想,到底是什麼問題致使了這種混亂。編程
首先來看一段代碼(Kotlin Code
):多線程
fun executeRequest(request: Request) : String { // 校驗身份 val isValidate = validateRequest(request) if( isValidate ) { return "Request is not valid" } // 處理業務 dealBiz(request) try { // 存儲數據 saveToDB(request) } catch (exception:Exception) { return "Occur error when save results to DB" } // 發送消息 val isSendSuccess = sendMessage(request) if (isSendSuccess == false) { return "message send unsuccessfully." } return "success" }
這是咱們比較常見的一些代碼結構,其實看起來問題也不算很大,可是隨着業務複雜,業務邏輯的控制和控制邏輯耦合的很厲害,閱讀這種"麪條代碼"的成本愈來愈高。每一位新進入項目的夥伴猶如進入了一個「代碼迷宮」。來來回回去尋找本身須要的那一段代碼,實際上這個時候已經造成了:架構
只有上帝和我能看得懂的「上帝代碼」了。併發
這顯然是咱們不肯見到的代碼,在左耳聽風專欄裏有一篇《編程的本質》裏講到:異步
有效地分離 Logic、Control 和 Data 是寫出好程序的關鍵所在。函數
那什麼又是Logic
,Control
,Data
呢?學習
dealBiz()
,sendMessage()
等等若是有效地將這幾種代碼分離,代碼可讀性將會大大提高。經過這種拆分,咱們也下降除了本身以外的維護者閱讀代碼翻譯業務內容的成本。經過分離,咱們能夠將代碼寫成這樣:測試
fun executeRequest(request: Request) : String { return Result .of(request) .flatMap { validateRequest(request) } .flatMap { dealBiz(request) } .flatMap { saveToDB(request) } .flatMap { sendMessage(request) } .fold( success = { return "success" }, failure = { return it.message } ) }
固然這裏說了如何寫細節的代碼,那麼代碼架構又如何去作纔可能保證能夠應對這麼多的變化?
通常的項目中咱們把一個軟件系統進行分層,這是咱們目前作工程項目的一個共識,咱們最初學習的分層架構就是經典的三層架構了。它自頂向下分紅三層:
到數據訪問層這塊,其實不少系統已經變成了面向數據編程,最終作成了「數據庫管理系統」。按照傳統的三層模型,用戶界面的開發依賴Service層,而Service層又依賴着DAO,DAO對應着數據庫。你們相互依賴,業務邏輯一旦修改,就意味着要從DAO層開始修改,數據庫也跟着被修改,而每每隨着咱們開發的深刻,業務的模型會被不斷調整,這樣數據庫可能就要頻繁的變更。代碼也開始變得複雜... ...
而在領域驅動設計中提出了另一種四層架構,在此以前,我想先分享《實現領域驅動設計》一書講的六邊形模型。
咱們在設計系統的時候,每每過於關注數據庫,Http接口等基礎設施的設計,而忽略了咱們須要關注的業務。在複雜系統中,最容易變化的也是業務形態,產品常常會要求改來改去,由於業務自己就在不斷地演進,若是咱們一開始就基於數據庫做全部的設計,那麼勢必一旦趕上業務的修改,庫表確定也須要對應先進行變化。假如咱們融入六邊形架構,將數據庫和暴露的Controller
都視爲是基礎設施,先去關注業務的模型和代碼,Class
的修改比要數據庫改起來要簡單的多。另一方面,也大大提升了程序的可測試性:在沒有準備一堆基礎設施(數據庫,接口,異步通知等等)狀況下,能夠先測試邏輯的完整性。
另外,有時候隨着業務增加有的基礎設施是會須要進行替換的,採用六邊形架構以後,這種更換的成本就會下降。另外若是出現須要使用Web Service的客戶,咱們也沒必要糾結於以前的HTTP接口,直接開出一套新的協議代碼供客戶使用,而不會糾結領域部分代碼有邏輯上的缺失。
採用六邊形架構以後,咱們的領域模型也會更加獨立,更精簡,在適應新的需求時修改也會更容易。在《架構整潔之道》之中提到的「整潔架構」也與「六邊形架構」大同小異。
其實這兩種架構也是依賴倒置原則很好的實現:
此時再回顧原來定義的四層架構:
他們的依賴關係如圖:
這樣咱們能夠將業務核心代碼放入領域層
之中,要應對的各個場景代碼放入應用服務層中。將協議轉換、中間件和數據庫的適配都放入基礎設施層裏。在應用層與Controller
之間的那些VO
做爲用戶展現層,以作出整潔架構。
當咱們開始學習Java的時候,都知道Java是一門面向對象的語言,咱們本能夠將現實世界翻譯到代碼的世界之中,但實際上咱們每每在項目中只會將對象定義成貧血模型,最終寫成面向過程的代碼。如何作才能讓這個複雜的世界反應到代碼裏呢?
讓咱們再從需求提及,對於一個複雜的軟件,任何一個項目的參與者(包括初創的成員),都很難靠本身就看清整個項目的全貌,咱們猶如圖中的盲人,你們可能最後對項目的理解都是不一致的。此時每一位參與者都猶如「盲人摸象」中的「盲人」,對需求(大象)只有片面的理解,因而乎,有的人以爲大象是水管的形狀,有的人以爲大象是扇子同樣的形狀,有的人說大象長得跟柱子同樣... ...
經過討論,咱們會對自個人認知進行一些修正,最終大體得出一個需求的全貌。
好比咱們要去識別一些系統邊界,在DDD的戰略設計中很是強調劃分界限上下文。好比我做爲一個個體,在不一樣的場景中,個人身份、角色都不大相同。
猶如在上圖中,在「地鐵」、「家庭」和「公司」中,個人身份是不同的,可是我依舊是我,找出業務的場景,也就意味着我找到了系統的邊界。經過分析場景識別邊界來找出系統的核心領域和支撐領域,以此來最終肯定系統的數量,下降系統的耦合。
咱們還能夠用四色建模方法來識別出咱們系統中發生的整個流程,發現到底是誰經過什麼方式觸發了什麼事情,最終又影響了哪些對象。
最終經過咱們找出的事件,整理出一個可以讓咱們進行溝通的模型。在咱們的模型被構建得相對完善之時,其實代碼也差很少已經被構建出來了,由於這個時候再去回想面向對象的設計,咱們發現模型即代碼,代碼即模型。
一直以來我都但願經過一些好的「工程實踐」來提升團隊的效率以及咱們的代碼質量,我想這也是思考這些架構的意義吧。我想用《架構整潔之道》中的一句話來作本文的總結:
軟件架構的最終目標是,用最小的人力成原本知足構建和維護系統的需求。
正如《人月神話》裏說的同樣,軟件工程裏沒有「銀彈」,即便作了整潔架構也沒法避免需求的變化和延期,只是但願當咱們身處需求的困境中時,仍能給本身以更多的選擇。