最近我發現團隊項目中的某個應用複雜度越來越高,具體表現為:
基于這些情況,我開始尋找降低複雜度的方案,于是就有了這篇再談DDD的文章。
1.1 具體問題1.1.1 宏觀角度從宏觀來說,軟件架構模式演進經曆了三個階段。
比如,在系統建設過程中,我們經常會看到這樣的情形:A 負責提出需求,B 負責需求分析,C 負責系統設計,D 負責代碼實現,這樣的流程很長,經手的人也很多,很容易導緻信息丢失。最後,就很容易導緻需求、設計與代碼實現的不一緻,往往到了軟件上線後,我們才發現很多功能并不是自己想要的,或者做出來的功能跟自己提出的需求偏差太大。
而且在單機和集中式架構這兩種模式下,軟件無法快速響應需求和業務的迅速變化,最終錯失發展良機。此時,分布式微服務的出現就有點恰逢其時的意思了。
上面這部分來自于極客時間,這裡面指出一般DDD是使用在微服務設計與拆分上,但我認為在單體應用中做模塊的拆分也是可以并推薦的,這可以讓你的模塊在需要時可以即刻拆分出去——變成一個獨立的微服務。相關可以參考【ZStack】4.進程内服務,這是一個開源,并實施于生産中很好的一個案例。
1.1.2 微觀角度這個問題很簡單,service的代碼必然會越堆越多,而且聚攏越來越多的業務。
image
2.DDD入門我們先來看一張圖:
從最外層開始——什麼是領域?大白話來說就是一系列問題的聚合。舉個例子:
可以看到,域是呈現出來的是一系列的業務領域問題。
在不同域中,同一個數據實體的抽象形态往往是不同的。比如,Bookstore 應用中的書本,在銷售領域中關注的是價格,在倉儲領域中關注的是庫存數量,在商品展示領域中關注的是書籍的介紹信息。
2.1 上下文邊界往裡面,我們應該看到的是限界上下文。其實這個翻譯并不好,原文叫bounded context,叫做上下文邊界更為妥當。本質上來說,它定義了邊界。再具體點,即:用來封裝通用語言和領域對象,提供上下文環境,保證在領域之内的一些術語、業務相關對象等(通用語言)有一個确切的含義,沒有二義性。
2.2 聚合接下來,我們看到了聚合。聚合就是由業務和邏輯緊密關聯的實體和值對象組合而成的,聚合是數據修改和持久化的基本單元,每一個聚合對應一個倉儲,實現數據的持久化。
聚合有一個聚合根和上下文邊界,這個邊界根據業務單一職責和高内聚原則,定義了聚合内部應該包含哪些實體和值對象,而聚合之間的邊界是松耦合的。按照這種方式設計出來的微服務很自然就是“高内聚、低耦合”的。
那聚合根是什麼呢?
聚合根的主要目的是為了避免由于複雜數據模型缺少統一的業務規則控制,而導緻聚合、實體之間數據不一緻性的問題。
傳統數據模型中的每一個實體都是對等的,如果任由實體進行無控制地調用和數據修改,很可能會導緻實體之間數據邏輯的不一緻。而如果采用鎖的方式則會增加軟件的複雜度,也會降低系統的性能。
如果把聚合比作組織,那聚合根就是這個組織的負責人。聚合根也稱為根實體,它不僅是實體,還是聚合的管理者。
首先它作為實體本身,擁有實體的屬性和業務行為,實現自身的業務邏輯。
其次它作為聚合的管理者,在聚合内部負責協調實體和值對象按照固定的業務規則協同完成共同的業務邏輯。
最後在聚合之間,它還是聚合對外的接口人,以聚合根 ID 關聯的方式接受外部任務和請求,在上下文内實現聚合之間的業務協同。也就是說,聚合之間通過聚合根 ID 關聯引用,如果需要訪問其它聚合的實體,就要先訪問聚合根,再導航到聚合内部實體,外部對象不能直接訪問聚合内實體。
2.3 實體與值對象在 DDD 中有這樣一類對象,它們擁有唯一标識符,且标識符在曆經各種狀态變更後仍能保持一緻。對這些對象而言,重要的不是其屬性,而是其延續性和标識,對象的延續性和标識會跨越甚至超出軟件的生命周期。我們把這樣的對象稱為實體。其實很像數據庫裡自帶不變id的一行行業務數據。
值對象相對不是那麼重要,因為它是用來描述實體的一組屬性集。很多系統中的實現會以json來實現,比如【ZStack】7.标簽系統。
為了方便理解,這邊做個小結。實體和值對象的目的都是抽象聚合若幹屬性以簡化設計和溝通,有了這一層抽象,我們在使用人員實體時,不會産生歧義,在引用地址值對象時,不用列舉其全部屬性,在同一個限界上下文中,大幅降低誤解、縮小偏差,兩者的區别如下:
這裡先簡單介紹一下三層模型到DDD對應的一個變化。
可以的看得出來,主要是對service進行了拆分。一般可以拆成三層:
我們可以用三步來劃定領域模型和微服務的邊界。
為了便于大家理解,我在這裡會設計一個很簡單的Iaas平台,并在裡面代入最基本的DDD概念。
3.3.1 産品願景串起來就是:為了滿足企業的内部的開發者和運維人員,他們的硬件資源管理,我們建設裡這個MiniStack,它是一個私有雲平台,它可以管理計算、存儲、網絡資源管理,幫用戶簡單快速的創建虛拟機,而不像OpenStack,我們的産品簡單、健壯、彈性。
3.3.2 場景分析因篇幅原因,我們來聊個最典型的場景——創建虛拟機,以便理出相關的領域模型。
在這裡我們需要注意,我們要盡可能地梳理整個系統發生的操作、命令、領域時間以及依賴變化等。
3.3.2.1 創建虛拟機但創建完虛拟機以後并不是就這麼完事了,萬一哪天這台物理機carsh了呢?哪天CPU因為奇怪的進程而打滿了呢?因此為了我們的目标——智能,創建vm後,MiniStac每5分鐘收集一系列的監控信息:
在這一步,我們需要對業務進行分析,建立領域模型。一般步驟為:
我們大緻可以找出幾個實體:
在找聚合前,我們先要找出聚合根。可以分為物理機、網絡、鏡像服務器、虛拟機。而他們彼此都是獨立的上下文,在需要的情況下,也可以拆成一個個微服務,如果是單體應用,則建議用模塊手段進行邏輯隔離。
3.3.4 微觀:領域對象與代碼結構分析
當我們完成宏觀上的建模後,便可以開始做微觀的事:梳理微服務内的領域對象,梳理領域對象之間的關系,确定它們在代碼模型和分層架構中的位置,建立領域模型與微服務模型的映射關系,以及服務之間的依賴關系。
大緻上,分為兩步:
在這一步,我們需要确認:
由于我們的用例比較簡單,整理如下:
接下來看一下聚合中的對象,我們把幾個聚合根識别出來:
而上面提到的實體屬性與方法我們已經在圖中呈現出來了。
關于值對象,可以參考【ZStack】7.标簽系統。該設計用于真實生産中。
3.3.4.2 設計代碼結構當我們完成領域對象的分析後,我們便開始設計各領域對象在代碼模型中的呈現方式了——即建立領域對象與代碼對象的映射關系。根據這種映射關系,服務人員可以快速定位到業務邏輯所在的代碼位置。
宏觀上,我們可以參考以下分層模型:
微觀實施上,我們可以參考COLA。
4.小結本文和大家一起捋了一遍DDD,并在文裡“憑空的”設計了一個項目。其實這個項目并非憑空,我參考了以前參與的開源項目ZStack并對它做出了簡化——該項目目前跑在大量的企業用戶的私有雲中,叠代已有6年多。因此無論從設計還是落地來說,都有一定的參考經驗。
為了大家方便将文中的例子結合ZStack代碼理解,我這邊做了一個映射。
,
更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!