對領(lǐng)域驅(qū)動設(shè)計的理解與實踐(對領(lǐng)域驅(qū)動設(shè)計的理解與實踐怎么寫)

領(lǐng)域驅(qū)動設(shè)計(domain-Driven-Design)是一種針對大型復雜系統(tǒng)的領(lǐng)域建模與分析方法論。

2003 年,Eric Evans 發(fā)布《Domain-Driven Design: Tackling Complexity in the Heart of Software》(領(lǐng)域驅(qū)動設(shè)計:軟件核心復雜性應(yīng)對之道),其中定義了DDD。

DDD改變了傳統(tǒng)軟件開發(fā)針對數(shù)據(jù)庫進行的建模方法;DDD先對業(yè)務(wù)領(lǐng)域進行分析,建立領(lǐng)域模型,根據(jù)領(lǐng)域模型驅(qū)動代碼設(shè)計。合理運用面向?qū)ο蟮母邇?nèi)聚低耦合設(shè)計要素,降低整個系統(tǒng)的業(yè)務(wù)復雜性,并使得系統(tǒng)具有更好的擴展性,更好的應(yīng)對多變的業(yè)務(wù)需求。

領(lǐng)域 Domain

一個領(lǐng)域就是一個問題域,只要是同一個領(lǐng)域,那問題域就相同。

只要確定了系統(tǒng)所屬的領(lǐng)域,那么這個系統(tǒng)的核心業(yè)務(wù),即要解決的問題以及問題的邊界就基本確定了。

舉例

陌生人社交領(lǐng)域,包含有聊天,用戶推薦,朋友圈等核心環(huán)節(jié)。 只要是這個領(lǐng)域,一般都會有這些相同的核心業(yè)務(wù),因為他們要解決問題的本質(zhì)是一樣的,就是交友。

每一個領(lǐng)域,都有一個對應(yīng)的領(lǐng)域模型,領(lǐng)域模型能夠很好的幫我們解決復雜的業(yè)務(wù)問題。

驅(qū)動設(shè)計

在DDD中,以領(lǐng)域(domain)為邊界,分析領(lǐng)域的核心問題,再設(shè)計對應(yīng)的領(lǐng)域模型,最后通過領(lǐng)域模型驅(qū)動代碼設(shè)計的實現(xiàn)。這樣設(shè)計的系統(tǒng)才有合理的分層與解耦,對以后業(yè)務(wù)的迭代開發(fā),代碼的維護才更加容易。

然而很多互聯(lián)網(wǎng)公司,為了追求快速的上線,都是模型都沒有想清楚就開始寫代碼,這就導致了后續(xù)代碼維護困難,無法擴展。修改bug同時又引入新的bug,反反復復,進入惡性循環(huán)。

當然,這跟梳理清楚領(lǐng)域模型需要一定時間,這與初創(chuàng)型的互聯(lián)網(wǎng)公司需求快速上線有點相悖,但是,這點時間的投入是非常值得的。因為可以避免了系統(tǒng)上線后不久又得重構(gòu)的問題。

概念總結(jié)

  • 領(lǐng)域就是問題域
  • 模型驅(qū)動的思想:通過建立領(lǐng)域模型來解決領(lǐng)域中的核心問題
  • 領(lǐng)域建模的目標:針對我們在領(lǐng)域中核心問題,而不是整個領(lǐng)域中的所有問題
  • 領(lǐng)域模型設(shè)計:設(shè)計時應(yīng)考慮一定的抽象性、通用性,以及復用價值
  • 代碼實現(xiàn):通過領(lǐng)域模型驅(qū)動代碼的實現(xiàn),確保代碼讓領(lǐng)域模型落地,代碼最終能解決問題

為什么需要DDD

系統(tǒng)復雜性

耦合

隨著產(chǎn)品不斷的迭代,業(yè)務(wù)邏輯變得越來越復雜,系統(tǒng)也越來越龐大。模塊彼此互相關(guān)聯(lián)、耦合。導致增加或修改一個功能變得異常艱難,同時功能間的界限也變得模糊,職責不再清晰。這個時候就需要進行重構(gòu),拆分。

雖然架構(gòu)本身是隨著業(yè)務(wù)進行不斷演進的;但是,如果架構(gòu)初始設(shè)計不體現(xiàn)出業(yè)務(wù)的模型,那么新需求就無法體現(xiàn)在現(xiàn)有架構(gòu)上,導致不斷腐化,不斷重構(gòu)。

內(nèi)聚

貧血模型 Anemic Domain Object

domain object僅用作數(shù)據(jù)載體,而沒有行為和動作的領(lǐng)域?qū)ο蟆?/p>

指領(lǐng)域?qū)ο罄镏挥術(shù)et和set方法,沒有相關(guān)領(lǐng)域?qū)ο蟮臉I(yè)務(wù)邏輯。業(yè)務(wù)邏輯放在業(yè)務(wù)層。

充血模型 Rich Domain Object

將業(yè)務(wù)邏輯和對象存儲放在domain object里面,業(yè)務(wù)層只是簡單進行小部分業(yè)務(wù)的封裝及其他domain的編排。

面向?qū)ο笤O(shè)計,符合單一職責設(shè)計。

貧血 vs 充血

貧血模型的domain object很輕量,這導致業(yè)務(wù)層的復雜,domain object相關(guān)的業(yè)務(wù)邏輯散布在各個業(yè)務(wù)層,造成業(yè)務(wù)邏輯的冗余以及原本domain object的定義就變得相對模糊,這就是貧血癥引起的失憶癥。

而采用領(lǐng)域開發(fā)的方式,將數(shù)據(jù)和行為封裝在一起,與業(yè)務(wù)對象相映射;領(lǐng)域?qū)ο舐氊熐逦瑢⑾嚓P(guān)業(yè)務(wù)聚合到領(lǐng)域?qū)ο髢?nèi)部。

微服務(wù)

DDD 的本質(zhì)是一種軟件設(shè)計方法論,而微服務(wù)架構(gòu)是具體的實現(xiàn)方式。微服務(wù)架構(gòu)并沒有定義對復雜系統(tǒng)進行分解的具體方法論,而 DDD 正好就是解決方案。

微服務(wù)架構(gòu)強調(diào)從業(yè)務(wù)維度來分治系統(tǒng)的復雜度,而DDD也是同樣的著重業(yè)務(wù)視角。

DDD能帶來什么

  • 建立通用語言: 圍繞領(lǐng)域模型建立的一種語言,團隊所有成員都使用這種語言進行溝通和活動
  • 驅(qū)動代碼設(shè)計:領(lǐng)域建立模型,模型指導設(shè)計,設(shè)計產(chǎn)出代碼
  • 解決核心問題:模型的設(shè)計中心就是核心域,就是解決核心的問題

DDD建模

戰(zhàn)略設(shè)計

戰(zhàn)略設(shè)計就是從宏觀角度對領(lǐng)域進行建模。劃分出業(yè)務(wù)的邊界,組織架構(gòu),系統(tǒng)架構(gòu)。

DDD中,對系統(tǒng)的劃分是基于領(lǐng)域的,也是基于業(yè)務(wù)的。

通用語言Ubiquitous Language

通用語言是指確定統(tǒng)一的領(lǐng)域術(shù)語,提高開發(fā)人員與領(lǐng)域?qū)<抑g的溝通效率。

一旦確定了統(tǒng)一語言,無論是與領(lǐng)域?qū)<业挠懻?,還是最終的實現(xiàn)代碼,都可以通過使用相同的術(shù)語,清晰準確地定義領(lǐng)域知識。

當確認整個團隊統(tǒng)一的語言后,就可以開始進行領(lǐng)域建模。

領(lǐng)域和子域

領(lǐng)域Domain

一個領(lǐng)域本質(zhì)上可以理解為就是一個問題域。只要我們確定了系統(tǒng)所屬的領(lǐng)域,那這個系統(tǒng)的核心業(yè)務(wù),即要解決的關(guān)鍵問題、問題的范圍邊界就基本確定了。

舉例

  • 社交領(lǐng)域:關(guān)鍵問題是用戶推薦,聊天
  • 電商領(lǐng)域:關(guān)鍵問題是購物,訂單,物流

子域Subdomain

如果一個領(lǐng)域過于復雜,涉及到的領(lǐng)域概念、業(yè)務(wù)規(guī)則、交互流程太多,導致沒辦法直接針對這個大的領(lǐng)域進行領(lǐng)域建模。這時就需要將領(lǐng)域進行拆分,本質(zhì)上就是把大問題拆分為小問題,把一個大的領(lǐng)域劃分為了多個小的領(lǐng)域(子域),那最關(guān)鍵的就是要理清每個子域的邊界

子域可以根據(jù)自身重要性和功能屬性劃分為三類子域:

  • 核心域:公司核心產(chǎn)品和業(yè)務(wù)的領(lǐng)域
  • 支撐子域:不包含決定產(chǎn)品和公司核心競爭力的功能,也不包含通用功能的子域
  • 通用子域:被多個子域使用的通用功能子域

每個領(lǐng)域的劃分都不一樣。對相同領(lǐng)域公司而言,其核心,支撐,通用的子域也可能有不一樣的地方,但大體上基本都是一樣的。

舉例

社交領(lǐng)域的劃分

  • 核心域:用戶推薦,聊天
  • 支撐子域:客服,反垃圾
  • 通用子域:消息推送

限界上下文Bounded Context

限界上下文

限界指劃分邊界,上下文對應(yīng)一個聚合,限界上下文可以理解為業(yè)務(wù)的邊界。

一個子域?qū)?yīng)一個或多個限界上下文。如果對應(yīng)多個上下文,則可以考慮子域是否要再進行細粒度的拆分。

限界上下文的目的是為了更加明確領(lǐng)域模型的職責和范圍

劃分限界上下文

三個原則:

  • 概念相同,含義不同(通用語言):如果一個模型在一個上下文里面有歧義,那有歧義的地方就是邊界所在,應(yīng)該把它們拆到不同的限界上下文中。
  • 外部系統(tǒng):有時候系統(tǒng)需要同外部系統(tǒng)交互,這時可以把與外部系統(tǒng)交互的那部分拆分出去以實現(xiàn)更好的擴展性。這樣一旦外部系統(tǒng)發(fā)生了變化,就不會影響到我們的核心業(yè)務(wù)邏輯。
  • 組織擴展:盡量不要兩個團隊一起在一個限界上下文里面開發(fā),因為這樣可能會存在溝通不順暢、集成困難等問題。

組織架構(gòu)

康威定律

任何組織在設(shè)計一套系統(tǒng)時,所交付的設(shè)計方案在結(jié)構(gòu)上都與該組織的溝通結(jié)構(gòu)保持一致。

團隊結(jié)構(gòu)就是組織結(jié)構(gòu),限界上下文就是系統(tǒng)的業(yè)務(wù)結(jié)構(gòu)。所以,團隊結(jié)構(gòu)應(yīng)該盡量和限界上下文保持一致。

舉例

社交領(lǐng)域中,訂單子域?qū)?yīng)訂單上下文

上下文映射

從宏觀上看每個上下文之間的關(guān)系,可以更好理解各個上下文之間的依賴關(guān)系。

梳理清楚上下文之間的關(guān)系是為了:

  • 任務(wù)更好拆分,一個開發(fā)人員可以全身心的投入到相關(guān)的一個單獨的上下文中
  • 溝通更加順暢,一個上下文可以明確自己對其他上下文的依賴關(guān)系,從而使得團隊內(nèi)開發(fā)直接更好的對接
  • 每個團隊在它的上下文中能夠更加明確自己領(lǐng)域內(nèi)的概念,因為上下文是領(lǐng)域的解系統(tǒng)

舉例

聊天上下文依賴消息推送,推廣上下文也依賴消息推送

戰(zhàn)術(shù)建模

戰(zhàn)術(shù)建模是從微觀角度對上下文進行建模。

梳理清楚聚合根,實體,值對象,領(lǐng)域服務(wù),領(lǐng)域事件,資源庫等。

實體Entity

當一個對象可以由標識進行區(qū)分時,這種對象稱為實體

和數(shù)據(jù)庫中的實體是不同的,這里的實體是從業(yè)務(wù)角度進行劃分的。

實體:

  • 具有唯一標識
  • 持久化
  • 可變

舉例

社交中的用戶即為實體,可以通過用戶唯一的id進行區(qū)分。

值對象value object

當一個對象用于對事物進行描述而沒有唯一標識時,它被稱作值對象。

在實踐中,需要保證值對象創(chuàng)建后就不能被修改,即不允許外部再修改其屬性。

例如:年齡,聊天表情符號( :stuck_out_tongue:: 吐舌 (U 1F61B))

習慣了使用數(shù)據(jù)庫的數(shù)據(jù)建模后,很容易將所有對象看作實體

聚合根Aggregate Root

聚合是一組相關(guān)對象的集合,作為一個整體被外界訪問,聚合根是這個聚合的根節(jié)點。

聚合由根實體,值對象和實體組成。(聚合根里面有多少個實體,由領(lǐng)域建模決定)

外部對象需要訪問聚合內(nèi)的實體時,只能通過聚合根進行訪問,而不能直接訪問

舉例

一個訂單是一個聚合根,訂單購買的商品是實體,收貨地址是值對象。

領(lǐng)域服務(wù)Domain Service

領(lǐng)域服務(wù)

一些既不是實體,也不是值對象的范疇的領(lǐng)域行為或操作,可以放到領(lǐng)域服務(wù)中。用來處理業(yè)務(wù)邏輯,協(xié)調(diào)領(lǐng)域?qū)ο髞硗瓿上嚓P(guān)業(yè)務(wù)。

例如,有些業(yè)務(wù)邏輯不適合放到領(lǐng)域?qū)ο笾?,或?qū)嶓w之間的業(yè)務(wù)協(xié)調(diào),這些業(yè)務(wù)邏輯都可以放到領(lǐng)域服務(wù)中。

特征

  • 與領(lǐng)域相關(guān)的操作如執(zhí)行一個業(yè)務(wù)操作過程,但它又并不適合放入實體與值對象中
  • 操作是無狀態(tài)的
  • 對領(lǐng)域?qū)ο筮M行轉(zhuǎn)換,或以多個領(lǐng)域?qū)ο笞鳛檩斎脒M行計算,結(jié)果產(chǎn)生一個值對象

當采用微服務(wù)架構(gòu)風格,一切領(lǐng)域邏輯的對外暴露均需要通過領(lǐng)域服務(wù)來進行。

如原本由聚合根暴露的業(yè)務(wù)邏輯也需要依托于領(lǐng)域服務(wù)。

舉例

必須通過訂單領(lǐng)域服務(wù)來創(chuàng)建和訪問訂單

領(lǐng)域事件

領(lǐng)域事件是對領(lǐng)域內(nèi)發(fā)生的活動進行的建模。捕獲一些有價值的領(lǐng)域活動事件。

作用

  • 解耦:可以通過發(fā)布訂閱模式,發(fā)布領(lǐng)域事件
  • 一致性:通過領(lǐng)域事件來達到最終一致性
  • 事件溯源

舉例

發(fā)送聊天消息,這屬于一個領(lǐng)域事件;撤回消息,也屬于一個領(lǐng)域事件。

推送服務(wù)訂閱消息事件,然后將消息推送給用戶端。這樣就解耦了消息服務(wù)與推送服務(wù)之間的強依賴關(guān)系。

資源庫Repository

資源庫用于保存和獲取聚合對象。

領(lǐng)域模型 vs 數(shù)據(jù)模型

資源庫介于領(lǐng)域模型(業(yè)務(wù)模型)和數(shù)據(jù)模型(數(shù)據(jù)庫)之間,主要用于聚合對象的持久化和檢索。

資源庫隔離了領(lǐng)域模型和數(shù)據(jù)模型,以便上層只需要關(guān)注于領(lǐng)域模型而不需要考慮如何進行持久化。

分層架構(gòu)

把一系列相同的對象進行分類放在同一層,然后根據(jù)他們之間的依賴關(guān)系再確定上下層次關(guān)系。

在實際決策時,我們需要知道各層的職責、意義以及相應(yīng)的場景;

落實到代碼層面時,我們還需要知道各層所包含的具體內(nèi)容、各層的一些常見的具體策略/模式、層次之間的交互/依賴關(guān)系。

DDD經(jīng)典分層架構(gòu)

對領(lǐng)域驅(qū)動設(shè)計的理解與實踐(對領(lǐng)域驅(qū)動設(shè)計的理解與實踐怎么寫)

  • 用戶接口層(interfaces):處理顯示和用戶請求,以及一些基本的參數(shù)檢查,不包括業(yè)務(wù)邏輯
  • 應(yīng)用層(application):主要協(xié)調(diào)領(lǐng)域?qū)ο蟮牟僮?;處理持久化事?wù)、發(fā)送消息、安全認證等
  • 領(lǐng)域?qū)樱╠omain):處理核心業(yè)務(wù)邏輯,不包括技術(shù)實現(xiàn)細節(jié)。領(lǐng)域?qū)邮菢I(yè)務(wù)軟件的核心
  • 基礎(chǔ)設(shè)施層(infrastructure):處理純技術(shù)細節(jié),為其他層提供技術(shù)支撐,也可用于封裝調(diào)用的外部系統(tǒng)細節(jié)。例如:持久化的實現(xiàn),消息中間件的實現(xiàn),工具類,rpc

個人理解:這種分層,既可以在一個單體應(yīng)用中,也可以是微服務(wù)的形式。DDD分層并不一定要按微服務(wù)的服務(wù)粒度進行分層。

如果一個業(yè)務(wù)邏輯非常簡單的子域,則可以將幾層都放進一個單體應(yīng)用中,在應(yīng)用中進行分層。如果業(yè)務(wù)較為復雜,則可以按服務(wù)進行拆分,每層都有自己對應(yīng)的服務(wù)。

其他架構(gòu)

  • 對稱性架構(gòu)
  • 洋蔥架構(gòu)
  • 整潔架構(gòu)
  • CQRS架構(gòu)

DDD工程實踐

以一個簡化的社交領(lǐng)域的例子來實踐DDD。

核心概念

  • 用戶(User): 一個賬戶,并以用戶id識別
  • 關(guān)系(Relationship):用戶之間的關(guān)系
  • 動態(tài)(Feed): 用戶發(fā)布文字,圖片,視頻,評論等內(nèi)容
  • 會話(Conversation):用戶之間的聊天會話

領(lǐng)域設(shè)計

戰(zhàn)略建模

領(lǐng)域就是社交領(lǐng)域,核心問題和絕大部分社交系統(tǒng)一樣。

子域

  • 核心域:聊天,動態(tài)
  • 支撐子域:反作弊,推廣
  • 通用子域:用戶,關(guān)系,消息推送

上下文

  • 消息上下文
  • 會話上下文
  • 動態(tài)上下文
  • 推送上下文
  • 用戶上下文

戰(zhàn)術(shù)建模

以會話上下文為例子來進行戰(zhàn)術(shù)建模

會話上下文

  • 會話:聚合根用戶:實體用戶:實體消息列表:實體發(fā)送人:實體接收人:實體消息內(nèi)容:值對象

消息在會話上下文屬于實體,在消息上下文屬于聚合根。

結(jié)構(gòu)

以會話子域為例

架構(gòu)分層

  • interfaces 接口層RESTfulRPC
  • application 應(yīng)用層Conversationmessage
  • domain_service 領(lǐng)域服務(wù)層modelConversationMessagerepositoryConversationMessage
  • infrastructure 基礎(chǔ)設(shè)施層storeConversationRepositoryMessageRepositorymessageSendMessageutils

領(lǐng)域

package domain// 聚合根type Conversation struct { ID int User1 User User2 User Messages list.List}// 實體type Message struct { ID int From User // 實體 To User Body Content // 值對象}

用戶接口層

type ChatInferface struct { // 應(yīng)用層 app app.ChatApplication}func (c *ChatInferface) Route() { c.route("POST", "/api/message", c.SendMessage) c.route("PATCH", "/api/message", c.RecallMessage)}// POST /api/messagefunc (c *ChatInferface) SendMessage(ctx *Context) { if !c.validateRequest(ctx) { return } message := c.parseMessage(ctx) app.SendMessage(message)}func (c *ChatInferface) RecallMessage(ctx *Context) { if !c.validateRequest(ctx) { return } messageID := c.parseMessage(ctx) app.RecallMessage(messageID)}

應(yīng)用層

type ChatApplication struct { user service.UserService chat service.ChatService // 這里領(lǐng)域事件由應(yīng)用層發(fā)布 // publisher EventPublisher lbs LBSFacade}func (c *ChatApplication) SendMessage(msg *Message) { if !c.user.CheckUser(msg.UserID) { return } c.chat.SendMessage(msg)}

領(lǐng)域服務(wù)層

type ChatService struct { // 領(lǐng)域事件 publisher MessageEventPublisher repo MessageRepository}func (c *ChatService SendMessage(msg *Message) { // 業(yè)務(wù)邏輯 ... // 領(lǐng)域資源持久化 c.repo.Save(msg) // 發(fā)布領(lǐng)域事件 c.publisher.Publish(msg)}

基礎(chǔ)設(shè)施層

package infrastructuretype MessageRepository struct { db MessageDatabase cache MessageCache}func (m *MessageRepository) Save(msg *Message) { db.Save(m.ToPO(msg))}func (m MessageRepository) Get(msgID int) *Message { msg := m.cache.Get(msgID) if msg != nil { return m.FromPO(msg) } return m.FromPO(m.db.Get(msgID))}

總結(jié)

在設(shè)計和實現(xiàn)一個系統(tǒng)的時候,這個系統(tǒng)所要處理問題的領(lǐng)域?qū)<液烷_發(fā)人員以一套統(tǒng)一語言進行協(xié)作,共同完成該領(lǐng)域模型的構(gòu)建,在這個過程中,業(yè)務(wù)架構(gòu)和系統(tǒng)架構(gòu)等問題都得到了解決,之后將領(lǐng)域模型中關(guān)于系統(tǒng)架構(gòu)的主體映射為實現(xiàn)代碼,完成系統(tǒng)的實現(xiàn)落地。

相關(guān)新聞

聯(lián)系我們
聯(lián)系我們
公眾號
公眾號
在線咨詢
分享本頁
返回頂部