戲說領域驅動設計(九)——架構模式
本節開始進入DDD的戰術階段,首先要講解的必然是DDD中的架構,畢竟程式設計師就喜歡這個……不過這裡的架構不同於我們常說的微服務架構、單體架構、無服務架構或服務網格。不嚴謹來講,上述4種為涉及到系統結構、部署方式、伺服器架構等更為全面的、包含軟、硬體等內容的宏觀系統架構(這個不在我們的主要範圍內,主要是以個人這點水平吹個牛還行,不成系統的)。而我們要講的更多的是聚焦於基於BC的架構模式,也就是BC在落地時所使用的設計模式。
提示: 嚴格上來講其實並沒有單體架構這個概念,微服務架構出來前大家一般都會把多個模組集中在一個程式集中,是一種約定俗成的模式,也沒人給它起個名。後來為了將其與分微服務架進行區分,才有了所謂單體的概念。 |
個人對於DDD中常用的的架構模式總結出了5種(總結出更多模式的大牛請嘴下留情):分層、Saga、洋蔥、命令查詢責任分離(CQRS)和事件源(ES),稍後會逐一進行講解。有人會說怎麼沒有六邊形?這東西其實和洋蔥是一樣的,您可以認為兩者相同。另外多說兩句,很多的文章都貼出來了六邊形或洋蔥以及非常有名的Clean Architecture架構並給出了大量的解釋,我個人認為這些都是同一種架構模式的不同描述(當然也不排除有些人故意讓你看不懂),不管名字叫得多邪乎,其實本質上是一個東西。比如有一張著名的架構圖,我就不信您看完了不暈(其實落地的時候還是分層的,再加上有了Spring這個神器,別說六邊了,十二邊咱也不懼)。
強調一點,我個人其實是一個經典三層架構模式的擁護者。雖然可以熟練的使用模型驅動的方式進行程式碼落地,但日常工作中選擇起來會比較慎重,能用三層的絕不用四層,能用面向過程的肯定不用面向對象,此乃我的懶人哲學觀。不過我可不管這個叫「懶」,這叫「務實」。另外,沒有任何模式是銀彈,您需要去取捨(當然了,取捨的前提是你得知道有哪些,其適合的場景是什麼。如果僅知道三層,那還取捨個毛線啊)。有的時候我覺得過度設計比不設計還要可怕。不設計的程式碼一般來說都是那種面向過程的,程式碼行數多,不過也比較直觀;那些設計過度的程式碼才是噁心,改一行程式碼幾乎需要走查所有的程式碼尤其作者不是自己的時候,一邊改一邊罵爹不說,你還不敢輕易上,萬一哪一塊沒考慮到呢?我依稀記得曾經入了一個群討論DDD問題:有幾個哥們在討論ES,有個牛人說他們的一個系統全面採用了ES(使用Axon框架)來實現,各種的好……我弱弱的問了一句ES一般是不是都是局部模式,僅適用一些特定的場景吧?結果直接被人家說「我們說的是事件源,不懂不要瞎說,不是你說的那個Elastic Search……」,不是我吐槽,這種就屬於典型的無知者無畏了。難聽的話咱不能再多說,忒丟份,但這類工程師夸夸其談、好大喜功,少了一些最起碼的職業素養。
一、分層架構
其實分層還真不能和CQRS、ES、Saga同日而語,這是一種基礎模式或可稱其為編程模式,後面三者是依據業務所需而建立起的一種設計模式。放在一起說其實有點驢唇不對馬嘴,不過也能加深我們的理解還有利於我多水一些文字。分層式開發雖然已經成為了一種事實上的標準,但就這樣,不會用的人也比比皆是。好多程式設計師連這個最基本的模式都搞不明白更別說更複雜的設計方法了,這還真不是技術不夠,是懶!這「懶」和我的「懶」不一樣。咱的「懶」是為了追求對設計的「度」的掌握;後面的「懶」那是真懶,誰告訴你開發就是無腦的堆程式碼的?就算是最簡單的3層也有許多的限制。我就見過一個方法超過1000行程式碼,其中0行是注釋。程式碼長不可怕,麻煩的是看到後面時把前面的給忘了!
一提分層架構您想到的肯定是經典三層,沒錯!個人很喜歡的一種模式,直觀!簡單!用起來老痛快了。其實,一個系統中的大部分的功能使用經典三層就可以搞定了除非您開發的是火箭導航這類系統。這話您可能會反對,您可能會說銀行啊、金融啊、保險啊這類系統,業務那麼複雜怎麼可能會使用三層?客觀來講,這類業務的複雜度不需要懷疑,但我個人認為越是複雜的系統越是會有一堆龐大的基礎設施和基礎業務做支撐,而一般基礎類業務的開發其實根本就用不著使用特別複雜的設計模式,那東西開發起來多費勁啊,有框架也不行。三層不代表程式碼品質差,高手寫起三層來照樣有大師的水準。技術的選擇永遠都是由於業務進行驅動的,切記切記。還記得上一章說的分層模式嗎?再啰嗦一句:三層模式請使用「嚴格分層架構」。下面貼了一張三層模式的圖,我知道你會,主要為了顯示我專業。對了,三層模式也叫「事務腳本」,Martin Flower稱其為「反模式」,主要是這種模式屬於典型的面向過程式編程,雖然入門簡單,但著實不適用於複雜業務的場景,擴展和維護起來忒費勁。
另外一種分層架構是DDD中所提的對象驅動設計模式(ODD,這名是我起的,其實就是面向對象編程,使用了如實體、值類型等DDD中戰術部分所提概念),共四層。這個模式使用起來約束比較多,是最最最基本的模式,說其是其它幾類架構的基礎也不為過。實際上,我後面所講的內容也都是以這模式為主的。這個模式有一個厚厚的業務模型層,依賴關係請參看下圖,幾乎各層都依賴於領域模型(我才不會讓用戶介面層直接指向BO,天機不可外泄),設計的時候也是從領域模型層開始,根據領域模型選擇資料庫。一會兒您把這個模式和後面說的洋蔥對比一下,是不是會發現所謂的洋蔥整個一忽悠人的,其實和下面的是一個玩意兒……
這裡,我概括出一個結論:分層模式是所有模式的基礎,使用面向對象編程時採用4層架構,否則使用經典3層。洋蔥模式或六邊形模式是一種概念形架構,通過在4層模式的使用中增加約束和規範即可達到目標,使用Spring後則更為簡單;後面提到的那四個貨是根據業務的所需(主要是非功能性需求)而使用的一種設計模式,落地後仍然是上面兩類分層模式之一。對於上面的結論您可能不認同,不過誰寫文章就聽誰的,反正我信了。
二、Saga
Saga模式主要解決兩個場景問題:分散式事務和多服務交互。Saga通過最終一致性的方式實現分散式事務,在使用的時候一般會用到MQ,所以務必要注意消息隊列的可靠性。Saga是一種EDA架構,其使用了兩個重要的領域模型:命令、事件,具體概念及案例後面安排。通過如下圖可以看出來,本模式引入了一個中心化的事務控制器(Saga有兩種模式:讓事務參與者作為協調者角色推動子事務的執行(去中心化Saga)和採用統一事務控制器模式(中心化Saga),個人比較傾向於第二種,所以這裡也僅介紹第二種),通過把一個大的、長的事務分成多個小業務片段(子事務)屬於比較典型的分治法。 中心化的事務控制器引入後可以實現事務參與者間的解耦,這種解耦也能緩解多服務交互時的複雜性。試想一下,如果有一個流程需要3個服務參與,就會存在多達6條鏈路;4個服務時需要12條鏈路……讓人望而生畏。
談到了事務就多說兩句,其一致性一般包含兩類:強一致性和弱一致性(後來有人覺得「弱」這個詞讓人很不爽,感覺就和「差」一樣,反正是一個貶義成份居多的詞,所以又給他起了個新名「最終一致性」)。強一致性的事務如本地事務、2PC、3PC等,這種屬於剛性事務,不太適合分散式系統而且性能也差了點意思。分散式系統已成為了當前的主流,CAP問題你是無法繞開的,所以大多數的分布試系統優先使用了「AP」。您想啊,分散式的一個主要目的就是為了提升系統的可用性,如果優先CP,那分散式優點就少了一大半。雖說弱化了「C」,但弱不代表沒有,這也是為什麼現在都講究最終一致性,其實是在分散式環境中所面臨的一種無奈,只能妥協。對了,最終一致性的事務了有個牛掰的名稱「柔性事務」,千萬別忘了。所以在這裡說了這麼多關於事務的事情,是因為Saga的一個主要作用就是做分散式事務,網上有相關論文您可以翻翻,雖然不一定用得上,但吹牛的時候還是有個談資的。另外,也有一些框架支援Saga比如:阿里的Seata、華為的ServiceComb,可以搞來玩玩兒。
我自己用的時候一般不使用上述框架,應用的場景也不是為了事務控制,而是做流程式控制制。比如一個業務流程需要A、B、C三個不同的服務節點參與完成,流程配置不同所使用的結點也不一樣,可能是「ABC」、「AC」、「ACBA」等,此等情形下通過引入Saga模式可以有效的解決服務調度繁瑣的情況(Saga會根據不同的事件觸發不同的命令,由事件來驅動業務流程的前行)。此處的流程調度控制器並不能代替工作流引擎,畢竟後者主要面向的是流程設計,也很少用於分布試場景下的服務調度。另外,自己寫一個簡單的Saga其實已經夠用了,這東西就和工廠、模板這種設計模式差不多,真心沒那麼神秘。
三、洋蔥
洋蔥或六邊型您可以認為是同一種模式,只是叫法不同而矣。前面我已經說過,這個模式是概念模型。其實現時一般會基於4層的面向對象編程架構,通常還需要有支援IoC的框架(如Spring)配合。當然,落地時還強制要求遵循一定的約束與規範,尤其是各層的責任和訪問順序都有著嚴格的限制。
四、命令查詢責任分離(CQRS)
CQRS本質上是一種在數據存儲和查詢方面進行優化的模式,並未引入什麼玄妙的概念。這個模式把命令(對數據有影響的操作)和查詢分成兩個不同的實現模式。命令端(C端)一般也是使用4層架構;查詢端(Q端)一般使用3層。通常來講,會有兩個不同的數據源(可能是同樣的庫也可能不同)分別用於查詢和命令場景,數據源之間使用事件進行同步。另外,使用CQRS時,C端一般使用非同步將參數以消息的形式發送到MQ然後立即回饋結果至調用端,速度那是相當的快;Q端可以使用您能使用的任何模式比如大表、快取等來加快查詢速度,反正只要夠快就行,唯一要注意的是Q端不能修改數據源。
這個架構主要是用於高並發的場景尤其是C端並發多時候,實際使用時需要考慮用戶的體驗畢竟調用後就返回給了客戶端,實際成功與否還兩說呢。數據不一致性是另外一個需要重點考慮的問題。此外,如果使用了ES(事件源)模式,肯定也得使用CQRS架構。因為ES寫入的是事件,而業務系統是需要查詢業務數據當前的值的。這裡有一個問題需要特別的說明,網上很多文章一說CQRS就習慣性和ES掛鉤,它們兩個的關係應該是這樣:使用了ES,一般會使用CQRS模式;使用CQRS不代表一定要使用ES。
另外,還有一種編程模式叫CQS(命令查詢分離),這個和CQRS有著本質的不同。前者一般只有一個數據源,C和Q端屬於同一個服務,C端一般也不使用非同步;後者在數據源上一般會多於1個,C和Q分屬兩個服務。CQS我認為是一種強制型編程模式尤其是使用ODD模式的時候,C操作使用4層而Q操作使用3層。也就是說,就算是同一個業務,其架構模式也不同,需要區分到查詢操作和命令操作。
五、事件源(Event Source)
事件源架構本身並無特殊性,只是其處理命令的時候存儲的是事件而不是直接更新資料庫表的值。您琢磨琢磨,一般我們存儲數據其實都是存儲其最終態,比如有個欄位「Status(狀態)」,其值為「3」。資料庫表直接存儲「3」這個值,至於這個值是從何而來,其變化軌跡如何等問題一般考慮也不會存儲起來。而ES記錄了一個對象狀態的變化軌跡,每一次變化對應一個事件,該模式適用於高並發且同時需要跟蹤對象變化的場景。由於存儲事件可以使用鍵值對(鍵:對象ID;值:事件)的方式,所以您可以想像一下,如果使用了比如HBase這種作為數據源,那速度絕對杠杠的。這個模式一般還會與CQRS模式配合,妥妥的一對好基友。查詢的場景一般是將對象的最終狀態供用戶使用,這個很簡單的需求對於事件源模式其實是非常困難的,ES架構下只存儲了對象所經歷的命令,只有把一個對象所經歷的事件重跑一次才能形成最終態;如果涉及對象列表或多表級聯查詢,那就不是ES能搞定的了更不要說未來的報表、數據倉庫等需求。所以可以想像,使用ES時必然需要同時有一個查詢庫配合才行。這種架構比較複雜,不是一個普適的模式。
使用ES的系統,運維起來也會是個噩夢。舉個例子,我在寫程式碼的時候由於考慮不周誤一個欄位的值變成了「8」,正確的應該是「7」,影響了1000條數據。正常情況下我直接修復程式碼中的BUG並批量更新資料庫的值,最多再多一步把快取幹掉,事情就結了。使用ES模式後,資料庫中存儲的是事件,想通過SQL直接改數據簡直難於上青天,等您寫好更新工具後搞不好還得同步整個辭職申請。
另外,ES屬於非常典型的事件驅動編程,其編程思維模式也與我們一般的方式不同,說白了就是有點彆扭,可見後面的案例。ES模式本人系統學習過但未曾在真實項目中使用,所以即使後面有案例您也只能參考使用,真要搞還得做好充分的心理準備以及相對成熟的框架做支撐。
上面介紹了5個模式,後面會進行詳細介紹。重點說明一下:分層模式才是本系列的重點,其它4種僅是補充,您需要根據實際業務的現狀和需求選擇對應的模式。還有一點,本文所說的東西可能與書上不一樣,也有可能會與您的認識不符,不過這是我個人的理解,反正您愛說什麼說什麼,我是虛心接受但堅決不改。畢竟「仰天大笑出門去 我輩豈是蓬蒿人」。