天天看點

DDD 領域驅動設計落地實踐系列:戰略設計和戰術設計

作者:慕楓技術筆記

#頭條創作挑戰賽#

引言

通過前面的文章介紹,相信大家對于什麼是 DDD 有了初步的了解,知道它是一種微服務的架構設計方法論,為我們解決如何建立領域模型,如何實作微服務劃分等提供了方向和指導。但是對于如何具體落地使用 DDD,可能大家還是一臉懵 B 的狀态,是以從本文開始以及後面的文章将對如何進行 DDD 落地進行詳細的闡述。在這其中還是會涉及到 DDD 中的一些重要概念,原本想着在一篇文章中介紹所有的概念,但是我覺得,概念總是在它該出現的時候出現才會讓大家印象深刻,否則這些概念隻是死闆的概念,我們不清楚他為什麼出現以及可以解決什麼問題。

DDD 大緻實作過程

如下圖所示,實作 DDD 落地大緻需要經曆這樣三個階段,即為業務分析-》戰略設計-》戰術設計,不同階段的輸出都是下一階段的輸入。業務分析階段為戰略設計輸出經過統一語言描述的業務事件、業務邏輯以及業務分類,而戰略設計階段又為戰術設計階段輸入領域模型以及邊界上下文,友善其進行微服務拆分以及模型映射。下面我們分别看下這三個階段到底都做了什麼事情。

業務分析:在這個階段需要集齊項目團隊的成員主要包括領域專家、設計人員、開發人員等一起對業務問題域以及業務期望進行全面的梳理,厘請業務中的統一語言,在業務領域中發現領域事件、領域對象及其對應的領域行為,搞清楚他們各自的關聯關系。

戰略設計:通過 DDD 的理論,對業務進行領域劃分建構領域模型,梳理出相應的限界上下文,通過統一的領域語言從戰略層面進行領域劃分以及建構領域模型。在建構領域模型的過程中需要梳理出對應的聚合、實體、以及值對象。

戰術設計:以領域模型為戰術設計的輸入,以限界上下文作為微服務劃分的邊界進行微服務拆分,在每個微服務中進行領域分層,實作領域模型對于代碼的映射,進而實作 DDD 的真正落地實施。

DDD 領域驅動設計落地實踐系列:戰略設計和戰術設計

相關概念補充

在介紹戰略設計和戰術設計之前,我們先來弄清楚一些晦澀難懂的概念性的内容,這些概念看上去總是不明覺厲。我覺得 DDD 不容易入門的一個原因就是這些概念太不好了解了,即便是《領域驅動設計:軟體核心複雜性應對之道》原著中的描述更加讓人難以捉摸。要想了解這些概念性的内容,我們要思考為什麼有這樣的概念,以及它的出現是為了解決什麼問題,在實際的上下文場景下是怎麼運用的,我想隻有這樣我們對這些不明覺厲的東東才能有更加深刻的了解。我盡量結合一些執行個體來進行說明,友善大家的了解。

1、值對象

值對象看上去又是比較抽象的概念,越抽象的概念我們越要抓住最主要的脈絡,才能了解。就值對象來說,我們可以把他了解沒有 ID 的東東,什麼叫沒有 ID 呢?可以這麼了解,當我們在外面吃飯的時候,在餐廳裡面選位子落座,我們會關心這個桌子是哪裡生成的,編碼是什麼以及規格是怎樣的嗎?顯然不會,有空位坐就可以了。當然我們還是需要關注值對象上下文的,如剛才的例子,我們消費者并不關心坐哪個位子,但是商家是關心的,因為他要根據桌号來進行上菜以及收賬。

再來舉一個例子,在我們設計使用者資訊的時候,這其中就會包括使用者的位址,使用者的位址實際是由這個使用者是哪個省的,哪個市的,哪個縣的以及具體位址是什麼、郵編是什麼等等屬性資訊組成,那麼這個位址資訊在使用者資訊中實際就是一個值對象,他不需要唯一的 ID 來辨別。

2、實體

和值對象不同的是,實體是有唯一辨別的,這就好比我們每個人都會有身份證,身份證上面的身份證号碼就是每個人的唯一辨別。另外這個身份證号碼會一直跟着我們不會改變,即使你改了名字戶口狀态變化,但是身份證号碼是不會變化的,這也是實體的另一個特征就是它具有延續性。實體對應了業務對象的業務屬性以及業務行為。

值對象以及實體都是領域模型中的領域對象,是組成領域模型的基礎元素,一起實作領域内的最基本的核心領域邏輯。

3、聚合

又是一個看上去不好了解的概念,實際上用大白話來說,如果人是一個個不同的業務實體,那麼社會中的不同組織、機構就是将對應技能的人聚集在一起發揮更大的業務價值以及完成更加複雜的業務行為的的集合。這就好比我們公司裡面有種各樣的部門,有人力部門負責招聘和薪酬、有銷售部門負責營銷、有研發部門負責産品研發。不同的部門實際就是不同的聚合。 聚合就是有業務關聯關系的實體以及值對象的集合,通過實體、值對象以及各自之間的業務邏輯聚合在一起完成某個業務節點,我們就可以了解為聚合。我們可以根據業務的單一職責以及高内聚的的設計原來來進行聚合的劃分。

4、聚合根

聚合的出現實際是一種業務單元,那它必定涉及到資料的持久化,如果在聚合中的任意實體都可以被外部進行資料修改,那麼我們将很難保證聚合内的資料一緻性。是以我們需要有一個資料輸入修改的統一入口來保證聚合内的資料修改統一的符合聚合中的業務規則,是以出現了聚合根的概念。聚合根實際是就是一種實體,具備唯一辨別,有獨立的生命周期。但是他是特殊的實體,他有協調實體以及值對象完成業務邏輯的功能,好比是一個部門的負責人一樣。一個聚合隻有一個聚合根,聚合根在聚合之内采用引用依賴的方式對實體和值對象進行組織和協調,聚合根和聚合根之間通過唯一 id 進行聚合之間的協同。

戰略設計

戰略設計這四個字聽起來有點高大上的感覺,感覺離我等 DS 比較遠。實際上無論對于公司或者個人來說,都需要有個發展的目标以及指導方針,指引我們該向何方前進,這個指導性的内容,我們就可以稱之為戰略。那麼在 DDD 領域,戰略設計主要從業務角度出發,劃分業務的領域邊界,建立基于通用語言和業務上下文語義邊界的限界上下文,實作業務領域模型的建構。使得限界上下文可以作為微服務拆分和設計的邊界。因為必須先有邊界清晰的領域模型以及上下文,我們才能設計出邊界清晰的微服務。是以在戰略設計階段最重要的就是限界上下文以及領域模組化。

通常在業務分析階段我們把業務域的主體流程進行了梳理以及分析,同僚将業務期望進行了定義。在進行戰略設計的過程中,需要對業務領域進行全面的梳理,首先對業務進行領域切分,劃分出業務的上下文邊界,然後建立對應的限界上文中的領域模型,上下文邊界和領域模型可以做诶微服務拆分拆分設計的輸入,指導微服務落地時的拆分設計。其中業務中,對應的領域模型十分重要。那麼我們該采用什麼方法才能從複雜的業務領域中建構出高内聚低耦合的領域模型呢?我們可以通過業務分析以及領域模組化兩個階段來實作領域模型的建構。

業務分析

1、事件風暴

通過事件風暴的方法可以實作快速分析和分解複雜的業務領域,分析并提取出對應的領域模型。事件風暴是早 2013 年提出來的,通過組織領域專家、以及項目團隊成員在一起通過頭腦風暴的方式,通過梳理業務領域中所有的領域事件以及領域事件的參與者以及輸入。

參與者

主要包括領域專家、DDD 專家、架構師、産品經理、項目經理、開發人員以及測試人員等

實際活動

當把參與者集合在一起之後,大家需要通過頭腦風暴的方式梳理目前的業務域問題。比如某個業務動作或者行為是否會觸發下一個業務動作,這個動作(領域事件)的輸入和輸出是什麼,是誰(實體)發出的什麼動作(指令),觸發了這個動作,這些我們都需要梳理清楚。

DDD 領域驅動設計落地實踐系列:戰略設計和戰術設計

我們以大家最熟悉的電商系統來舉個栗子,在電商業務中有一個重要的環節就是物流。是以物流是電商業務的重要業務子域。當使用者下完訂單之後,在倉儲領域就需要陳誠對應的貨物揀貨單,系統根據揀貨單中的貨物給不同分區的倉内作業人員生成對應的揀貨任務,作業人員根據對應的揀貨任務進行貨品的揀取,并放入相應的貨物容器中。最終将所有訂單商品在揀貨完成後進行合流,進行校驗,最終形成包裹再進行後續的配送流程。

那麼在這個流程當中,我們就會涉及到的實體就有訂單、揀貨單、揀貨任務、使用者、容器等,對應的值對象為位址資訊。領域事件為揀貨單生成、揀貨任務生成。

領域模組化

在我看來,領域模組化實際上是整個 DDD 領域驅動設計中最重要的環節。對上,好的領域模型說明對業務流程的梳理以及業務領域的抽象做得好。對下,高内聚低耦合的領域模型可以直接決定微服務設計的品質和水準。

1、找出實體和值對象

經過全盤的業務梳理之後,我們可以在業務對象中找到對應的實體以及值對象,這裡面就會牽扯到哪些領域對象設計未實體,哪些領域對象設計為值對象。主要還是根據實際的業務特征來決定。

2、建構聚合

通過業務梳理,我們找到了業務下所有的實體以及值對象,接下來我們就要建構聚合了。在建構聚合之前,我們需要先從實體集合中找到聚合根,這就好比打仗的時候講究擒賊先擒王,王擒到了之後,歸屬于下面的小兵就會乖乖就範了。

那麼我們應該怎麼判斷一個實體它就是聚合根呢?主要是看這個實體是不是有專門的子產品來進行維護,自身是不是有完整的獨立的生命周期以及是不是有全局的唯一 ID。通過這幾個判斷條件我們很容易找到對應的聚合根,如下圖所示,在倉内進行作業的任務,其中揀貨單就是一個聚合根,滿足上述的幾個條件,同時可以将和其有業務關聯的實體例如貨物、揀貨容器等歸并到揀貨單,最終形成揀貨聚合。

DDD 領域驅動設計落地實踐系列:戰略設計和戰術設計

3、劃分聚合到邊界上下文

首先我們需要将之前梳理出來的領域事件,事件流轉的觸發指令都全部羅列出來,在這個過程中提取出産生業務行為的對象,就是前文所說的實體。如前面所說的物流域,庫存、容器。在這個過程中我們需要找出對應的實體以及值對象,同時在這些實體中找出聚合根,将存在存在緊密業務邏輯關系的聚合根、實體以及值對象劃分到一起形成聚合,再根據之前劃分的邊界上下文将多個聚合滑到限界上下文中。

戰術設計

不同于戰略設計的高屋建瓴,戰術設計是從實際的技術角度出發,它更加側重于領域模型的技術實作,按照領域模型完成微服務的開發以及落地。從某種意義來說,戰略設計實際就是戰術設計的輸入。在戰術設計中會有聚合、聚合根、實體、值對象、領域服務、領域事件的代碼實作,通過将這些領域對象映射到代碼中實作設計以及系統的落地。在這個階段,我們需要梳理微服務的邊界以及邊界内的領域對象以及它們之間的關系,并且可以實際的将這些領域模型确定在哪些代碼子產品中以及微服務分層中的具體位置。

通過 DDD 按照一定的規則對業務領域進行細分,是的問題範圍可以限定在指定的邊界中,是以我們可以在這個邊界内建立領域模型,而後再通過代碼實作領域模型,進而解決相應的業務問題。

是以在戰術設計階段,我們有個重要的事項需要去完成,一個是微服務的領域對象分析與邊界劃分,另一個是微服務的結構分層。

微服務領域對象分析與邊界劃分

在戰略設計階段,我們已經建構的業務領域模型,領域模型中包含了實體、值對象、聚合根。在上文中我們實際已經根據一些業務域進行了聚合的劃分,那麼在此處我們需要根據不同的聚合以及已有的限界上下文繼續劃分微服務。有的微服務會包含多個聚合,有的微服務隻有一個聚合。

DDD 領域驅動設計落地實踐系列:戰略設計和戰術設計

微服務結構分層

在傳統的工程代碼結構中,大都采用 MVC 的工程結構。但是随着微服務時代的來臨,傳統的工程結構不能滿足快速變化的業務需求。另外随着 DDD 領域驅動設計的落地,需要對于微服務的工程結構有更進一步的進化和更新。

DDD 領域驅動設計落地實踐系列:戰略設計和戰術設計

Eric Evans 在《領域驅動設計:軟體核心複雜性應對之道》文中提出了傳統的四層結構,但是實際上存在一定的問題。在 DDD 領域驅動設計中,領域層應該是核心層,但是傳統的四層結構中基礎層卻處在核心位置,有點本末倒置的感覺。是以應該采用以領域層為核心的新的四層結構的方式優化原有的結構,實作對于基礎層的解耦。

基礎層:實際就是未微服務的其它層提供基礎的通用技術支撐,如 Redis、MQ 以及資料庫等,常見的就是資料庫持久化可以放在這層當中。

使用者接口層:負責向使用者展示資訊以及轉化使用者請求意圖,在前後端分離的當下,可以實作在後端服務不變的情況下适配不同的使用者前端的作用。

應用層:可以了解為實作領域對象以及多個聚合的服務編排以及組合,不應該有過多的業務邏輯。

領域層:領域層實際就是整個微服務的核心,在這其中涉及到領域對象的㛑狀态變化以及領域規則。領域層主要包括實體、值對象、聚合根以及領域服務等。

當然實際上給還有其他的分層結構比如五層分層結構、六邊形分層結構。

總結

本文主要圍繞 DDD 領域驅動設計落地時間的三大過程進行了闡述,詳細說明了戰略設計階段以及戰術設計階段的輸入和輸出。希望對于大家怎麼去實施 DDD 領域驅動設計有一個方向的指引,後面的文章将繼續深入闡述 DDD 具體落地實踐的細節和一些建議。

繼續閱讀