實踐篇 | DDD概念複雜難懂,實際落地如何設計程式碼實現模型?

韓楠發表於2022-06-17




寫在前面:


今天我接著跟大家聊一聊,DDD概念複雜難懂,實際落地如何設計程式碼實現模型。或許你是剛看到關於這部分的內容,想著這裡我有必要多說一句,關於這個話題,框架上,分為這樣兩部分講的:方法篇 + 實踐篇。


前一部分 ,方法篇。段首,加了超連結,點選可實現跳轉閱讀 這一講 旨在詳細介紹DDD所包含的幾個核心概念,以及圍繞這些概念所構建的DDD程式碼實現模型的組成結構。至於為何有必要講,上一篇開頭我有明確告訴大家


另外, 考慮到有的朋友可能才剛點進來,還沒看過上一篇,或者沒來得及看,故而這裡也再點明說一下 我想分享這一話題的必要性,以便於幫你快速知曉可以或多或少有哪方面的收穫。


開門見山說,可惜的是, 目前業界關於如何實施這些概念,並沒有一套統一的標準和規範, 這就導致我們在具體的開發過程中,常常感到無從下手。

為此, 本文專門提煉了一整套DDD程式碼實現模型。


此外,關於看的方式,我多說一句。基於是分為前後兩部分更新發布的,這就涉及到先後了。若上一篇你還未看,朋友,建議你可以先花上幾分鐘,或者結合文章中大小標題的思路引導,大致瞭解下行文框架。我們們可以過完上一篇,再進入這篇的分享,結合著,效果更好。




責編 | 韓楠

約 3685 字 | 7 分鐘閱讀





 以下,Enjoy~ 



接下來,我們們開始進入正文。 上一篇中, 詳細介紹了DDD所包含的一系列核心概念,以及圍繞這些概念所構建的DDD程式碼實現模型的組成結構。要想讓這些程式碼實現模型真正落地,我們需要把它們與具體的應用場景結合起來。


承接上一講,本篇為 實踐篇, 將詳細闡述DDD程式碼實現模型的設計方法,並給出一個具體的案例分析。



01   如何設計DDD程式碼實現模型?


在分析DDD程式碼實現模型時,對於上一篇提到的四個組成部分,我們需要梳理它們的 程式碼結構和依賴關係 。針對程式碼結構,我們需要明確程式碼包的組成,以及內部所包含的技術元件。


在明確了包結構之後,依賴關係指的是我們需要進一步明確這些程式碼包和技術元件之間的互動關係。基於這兩點,讓我們先來討論領域物件 的程式碼實現模型。


▶︎   領域物件程式碼實現模型


針對領域物件,我們通常用“domain”這個單詞,對程式碼包結構的頂層包進行命名,在該包結構下的所有技術元件,都屬於領域物件的範疇。


具體而言,在DDD中,領域物件包括領域模型物件、領域事件、資源庫以及應用服務所涉及到的命令和查詢物件,其中領域模型物件可以分為聚合、實體和值物件這三大類。


因此,在DDD所有的程式碼實現模型中,領域物件 涉及的程式碼結構最為複雜 ,可以分成 兩個層次 ,如圖1所示。

圖1

可以看到,這裡的 domain 代表整個領域物件,而 model 則代表領域模型物件,請注意這兩者在命名上的區別 以及它們之間的從屬關係。領域物件是DDD程式碼實現模型的基礎,包含核心業務邏輯的實現。


▶︎   應用服務程式碼實現模型


類似地,針對應用服務,我們通常使用“application”來命名頂層包結構。應用服務包含查詢服務和命令服務這兩大類,所以在子包的命名上,也會用“commandservice”和“queryservice”加以區分,如圖2所示。


圖2

顯然,命令服務和查詢服務,分別依賴於領域物件程式碼實現模型中的命令物件和查詢物件,我們用虛線表示這層依賴關係。在DDD的程式碼實現模型中, 應用服務可以說是互動關係最為複雜的一個程式碼 模型。

一方面,它需要將命令和查詢操作,分派給聚合物件等領域模型物件。

另一方面,它也需要分別和基礎設施,以及其他限界上下文進行互動。


關於後者,我們在討論到案例分析時,還會做進一步展開。

▶︎   基礎設施程式碼實現模型


其實,所謂的基礎設施,指的是DDD應用程式中所使用到的各種具 體技術、工具和框架。常見的基礎設施類元件主要包括這幾個方面:


  • 資料持久化(Persistence)

  • 訊息通訊(Messaging)

  • 系統配置(Config)

  • 安全控制(Security)


因此,基礎設施的包結構 並不是固定的 ,而是根據具體的技術開發要求進行靈活的組織,這裡給出一個常見的包結構,如圖3所示。針對基礎設施,我們使用了“infrastructure”,對這一包結構進行命名。


圖3

上圖中 有一點需要注意 ,代表資料持久化的“persistence”包,和代表訊息通訊的“messaging”包,在基礎設施程式碼實現模型中是最常見的,因為它們分別對應著領域物件中的資源庫和領域事件。


在DDD中,資源庫和領域事件的定義位於領域物件程式碼實現模型中,它們與具體的實現技術無關。而與具體實現技術相關的持久化和訊息通訊,則位於基礎設施程式碼實現模型中。這裡體現了 領域物件與實現技術相互分離的設計原則。

▶︎   上下文整合程式碼實現模型


最後,我們來討論上下文整合程式碼實現模型。 需要注意的是 ,這個 模型實現起來難度最大 ,因為涉及到多種系統整合技術體系。


針對這一程式碼實現模型,我們首先需要明確它是面向多個限界上下文的,所以我們需要考慮資料的流向,也就是所謂的 內向(Inbound)資料和外向(Outbound)資料。


一方面,限界上下文,需要暴露訪問入口供其他上下文進行使用。站在當前上下文角度看,這是一個Inbound操作。而當某一個上下 文向外部上下文發起請求時,這就是一個Outbound操作,如圖4所示。


圖4

在程式碼實現模型的設計上,我們也將採用“inbound”和“outbound”來命名包結構。那麼 這兩個包結構下,應該包含哪些技術元件呢?


我們先來討論“outbound”包結構,如圖5所示。 圖中,“rest”包中的REST API將外部請求,轉化為內部的Command和Query物件,並交由應用服務進行處理。在這個轉化 過程中,通常需要引入專門的DTO(Data Transfer Object,資料傳輸物件)物件,和組裝器(Assembler)物件。

圖5

同時,“eventpublisher”包中的 事件釋出器 (Event Publisher) ,則用來面向外部限界上下文釋出領域事件。


接著,我們討論“inbound”包結構。在一個限界上下文中,資料的Inbound操作主要有兩類,一類是 防腐層(Anti-Corruption Layer,ACL) ,用來向遠端REST API發起請求並獲取結果。另一類 是用來完成對領域事件進行響應的 事件處理器(Event Handler) ,如圖6所示。


圖6

基於上下文整合過程,兩個上下文中的“inbound”和“outbound”包結構中所包含的技術元件,實際上是一一對應的,如圖7所示。


可以看到,一個限界上下文“inbound”中的“acl”和“eventhandler”,分別對應著另一個限界上下文“outb ound”中的“rest”和“eventpublisher”。


圖7

至此,關於DDD中四大類程式碼實現模型,已介紹完。在接下來的內容中,我們將 基於一個具體的應用場景,透過案例分析,將這些程式碼實現模型付諸於實踐。基於這個案例,你可以將本文前面介紹的所有內容,和日常開發過程聯絡起來,進一步掌握將模型轉化為具體程式碼的實現方法和技巧。




02   DDD程式碼實現模型案例分析


在現實世界中,工單處理是一個非常常見的業務需求。而工單的發起,通常都是因為使用者需要對訂單進行諮詢或投訴。


在這個場景中,基於DDD的設計方法,我們可以分別拆分出 工單(Ticket)、客服(Staff),以及訂單(Order) 這三個限界上下文。在這三個上下文中,Ticket上下文,會分別與Staff和Order這兩個上下文進行整合,從而建立工單申請,如圖8所示。


請注意,圖中展示了Ticket上下文,所具備的兩種不同的上下文整合方 式。


針對Staff上下文,Ticket上下文將使用REST API,完成對工單中客服資料的獲取。

而針對Order上下文,則使用了領域事件,即一旦Order的狀態發生變化,Order上下文會傳送對應的領域事件到Ticket上下文中。

圖8


▶︎   Ticket上下文程式碼實現模型示例

顯然,針對這一場景,Ticket上下文同時具備了Inbound和Outbound操作。因此,它的程式碼實現模型是最完整的,如圖9所 示。


圖9


上圖中,我們使用IDEA這款開發工具和Spring Boot這一特定的開發框架,構建了Ticket限界上下文的程式碼實現模型。我們可以很清晰地看到,DDD四種程式碼實現模型的表現形式,就是五個頂層的程式碼包結構。其中,上下文整合程式碼實現模型同時包含了“inbound”和“outbound”這兩個程式碼包。


我們再對這些頂層程式碼包結構做展開,可以得到如圖10所示的子程式碼包結構。


圖10


上圖所示的所有子程式碼包結構,在前面的內容中也都已經給出了相應的描述,這裡便不再贅述。


Ticket上下文中,命令服務TicketCommandService 完成了對Staff服務的上下文整合,這時候採用的是防腐層ACL元件,示例程式碼如下所示。


可以看到,這裡使用AclStaffService這個ACL元件,對Staff服務發起了遠端呼叫,然後把返回結果填充到命令物件,並建立Ticket聚合 。最終,我們透過TicketRepository完成了對聚合物件的持久化操作。


圖11


上述AclStaffService,就完成了對Staff上下文所提供的REST API的呼叫,示例程式碼如下所示。這裡用到了Spring自帶的RestTemplate模板工具類,完成對遠端HTTP端點的訪問操作。

圖12


▶︎   Staff上下文程式碼實現模型示例

在Staff上下文,我們需要完成對上述REST API的構建,它的程式碼工程結構如下圖所示。


可以看到,相較Ticket上下文,Staff上下文的程式碼結構比較簡單,因為該上下文只需要提供對外的“outbound”包,而基礎設施部分也只需要完成對領域物件的持久化操作即可。


圖13


▶︎   Order上下文程式碼實現模型示例

最後,我們來到Order限界上下文,它的程式碼實現模型是這樣的,可以一同看下。


圖14


我們知道Order上下文,提供了針對Order資料的領域事件釋出機制,所以它的“outbound”包中包含了用於釋出領域事件的“eventpublisher”子包,並提供了一個OrderEventPublisherS ervice,如下所示。 


圖15


這裡透過Spring Cloud Stream,實現了領域事件的釋出。而在Ticket上下文 中,我們同樣可以基於Spring Cloud Stream,實現對該領域事件的監聽和消費,示例程式碼如下所示。


圖16

請注意,上述OrderUpdatedEventHandler,位於Ticket上下文“inbound”包的”eventhandler”子包中。


關於這些具體實現程式碼的講解不是本文的重點,你可以參考筆者在Github上的案例程式碼進行系統學習:https://github.com/tianminzheng/customer-service。




03   總結和延伸思考


今天的分享到這裡就結束了。本文內容詳細回答了開發人員,在實現DDD應用程式中所碰到的一個核心問題,即如何構建DDD的程式碼實現模型。之所以要討論這個話題,原因在於DDD中的很多概念都比較晦澀難懂,而業界也沒有為如何實現這些概念,提供統一的開發規範和標準。


而透過將DDD中的各種複雜概念與具體程式碼實現模型進行對映,在幫我們更好地理解這些概念的同時,也能夠將它們直接應用到日常開發過程中。


透過本文內容的介紹,開發人員可以結合自身的業務開發需求,設計一套完整的DDD程式碼實現模型。這裡也附上全文思維導圖,助你回顧、梳理思路等。


圖17 全文思維框架導圖-幫助你快速回顧、梳理、總結

▶︎   最後,我覺得還是有必要強調一點

本文中給出的DDD程式碼實現模型,也只是一個參考模型。而 程式碼實現模型的設計,也與具體所採用的技術體系有一定關聯。在本文所展示的案例中,我們使用了Spring Boot、Spring Cloud Stream等Spring家族中的開發框架,來開發DDD應用程式。


而如果你使用Axon這種基於事件溯源模式的DDD開發框架,那麼在程式碼實現模型中,就需要引入用於事件分發和儲存的Gateway、EventStore等元件,而位於基礎設施中的傳統資料持久化元件,可能就不一定會被使用到。


當然,基於我們今天介紹的內容,相信你並不難對這套DDD程式碼實現模型進行擴充套件。DDD作為一種系統建模方法論,也存在一些諸如分層架構、整潔架構、六邊形架構等多種架構風格。


針對每種架構風格,我們都需要設計對應的程式碼實現模型。


而基於本文中介紹的內容,透過對DDD中各個核心概念與實現模型之間進行合理的對映,我在文中 提供了一套設計程式碼實現模型的系統方法, 從而幫助你可以應對不同架構風格的實現要求。


這也是本文的核心價值所在。


今天的分享到這裡就結束了,期待留言區裡的進一步交流,也可以把它分享給你的朋友。我們後續再會。





THE END 

轉載請聯絡ITPUB官方公眾號獲得授權

—————————————————————————————————

歡迎各領域技術人員投稿

投稿郵箱 |    hannan@it168.com








來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70016482/viewspace-2901316/,如需轉載,請註明出處,否則將追究法律責任。

相關文章