前幾年就開始接觸DDD(Domain Driven Design,領(lǐng)域驅(qū)動設(shè)計),并且著迷于此。它更多地在戰(zhàn)略層指導(dǎo)了我的設(shè)計,對于戰(zhàn)術(shù)層面的設(shè)計,目前業(yè)界沒有統(tǒng)一的標(biāo)準(zhǔn),也沒有特別流行的方案。雖然也有許多技術(shù)大牛們熱衷于DDD,但一到代碼落地便一地雞毛,造不出“銀彈”。
那DDD到底是什么呢?有什么技術(shù)落地方案呢?今天我來給大家科普一下。
基本概念
過去系統(tǒng)分析和系統(tǒng)設(shè)計都是分離的,正如我們國家“系統(tǒng)分析師” 和“系統(tǒng)設(shè)計師” 兩種職稱考試一樣,這樣割裂的結(jié)果導(dǎo)致,需求分析的結(jié)果無法直接進(jìn)行設(shè)計編程,而能夠進(jìn)行編程運行的代碼卻扭曲需求,導(dǎo)致客戶運行軟件后才發(fā)現(xiàn)很多功能不是自己想要的,而且軟件不能快速跟隨需求變化。
DDD則打破了這種隔閡,提出了領(lǐng)域模型概念,統(tǒng)一了分析和設(shè)計編程,使得軟件能夠更靈活快速跟隨需求變化。
DDD專門為解決復(fù)雜性而誕生,因此解決思路完全不同于傳統(tǒng)的CRUD,但是DDD本身掌握起來并不會感覺復(fù)雜,從程序員角度看,DDD其實是研究將包含業(yè)務(wù)邏輯的ifelse語句放在哪里的學(xué)問。
Q:DDD有什么作用?
A:想想我們做軟件設(shè)計的初衷是什么?
通過微服務(wù)與領(lǐng)域驅(qū)動設(shè)計,簡化設(shè)計,降低維護(hù)成本,提高軟件交付速度。這要求我們落地的時候,架設(shè)一個支持微服務(wù)、支持領(lǐng)域驅(qū)動的架構(gòu)。
Q:DDD適用于什么場景?
A:在Evans寫的《領(lǐng)域驅(qū)動設(shè)計》一書,副標(biāo)題已經(jīng)說明一切:軟件核心復(fù)雜性應(yīng)對之道。領(lǐng)域驅(qū)動的真正作用,在于項目中對日后的維護(hù),當(dāng)系統(tǒng)變得越來越復(fù)雜的時候,才能體現(xiàn)出它的威力。
那新項目該不該用領(lǐng)域驅(qū)動?新項目并不復(fù)雜,產(chǎn)品還在跑模式的階段,雖然它的優(yōu)勢并不能真正發(fā)揮出來,但我認(rèn)為,DDD同樣適用新項目。項目只會越做越復(fù)雜,我們從一開始就應(yīng)該考慮日漸發(fā)胖的代碼、隨時可能獨立的子業(yè)務(wù)。而新項目更多的是指導(dǎo)戰(zhàn)略層設(shè)計(如領(lǐng)域建模),戰(zhàn)術(shù)層的技術(shù)落地還是以團(tuán)隊成員最熟悉的方式進(jìn)行,目標(biāo)是持續(xù)快速交付、降低維護(hù)成本。
領(lǐng)域建模
Q:什么是領(lǐng)域模型?
A:為解決場景下的問題而形成的一套模型,然后使用這套模型來解決業(yè)務(wù)問題。 根據(jù)重復(fù)勞動經(jīng)驗我們會形成一套模式,領(lǐng)域模型也一樣會形成一套模式,包括:實體、值對象、模塊、領(lǐng)域服務(wù)。
領(lǐng)域發(fā)現(xiàn)
那領(lǐng)域模型是怎么一步一步確定下來的呢?
推薦兩種比較常用的領(lǐng)域發(fā)現(xiàn)方法:事件風(fēng)暴與四色建模法。
一、事件風(fēng)暴
Event Storming(事件風(fēng)暴)是一種輕量級的系統(tǒng)分析方法,基于 DDD 的概念,能夠為我們梳理系統(tǒng)中的各種相關(guān)元素,其中包括了核心的 Aggregate。它能夠幫助開發(fā)人員梳理核心的業(yè)務(wù)對象,從某種程度上來說就是就是領(lǐng)域?qū)ο笾械木酆稀?/p>
描述產(chǎn)品愿景
產(chǎn)品愿景的主要目的是對產(chǎn)品頂層價值的設(shè)計,使產(chǎn)品目標(biāo)用戶、核心價值、差異化競爭點等信息達(dá)成一致,避免產(chǎn)品偏離方向。
產(chǎn)品愿景的參與角色:領(lǐng)域?qū)<?、業(yè)務(wù)需求方、產(chǎn)品經(jīng)理、項目經(jīng)理和開發(fā)經(jīng)理。
事件風(fēng)暴關(guān)注點
事件:某個動作的結(jié)果
屬性:事件的輸入、輸出
命令:某個動作
實體:命令的觸發(fā)者
簡單理解就是誰(實體)使用什么(輸入)做了什么(命令、動作)產(chǎn)生了什么(輸出)影響了什么(事件)。

二、四色建模法
通過還原業(yè)務(wù)邏輯事件,依據(jù)是否影響公司的運營和發(fā)展,確定憑證作為時標(biāo)型對象,并補(bǔ)全相關(guān)描述的建模方法。四色建模法一般運用于問題分析建模。
四色建模是哪四色?
它包括:
時標(biāo)型(Moment-Interval)對象:具有可追溯性的記錄運營或管理數(shù)據(jù)的時刻或時段對象,用粉紅色表示;
PPT(Party/Place/Thing)對象:代表參與到流程中的參與方/地點/物,用綠色表示;
角色(Role)對象:在時標(biāo)型對象與 PPT 對象(通常是參與方)之間參與的角色,用黃色表示;
描述(Description)對象:對 PPT 對象的一種補(bǔ)充描述,用藍(lán)色表示。
分析的五個步驟
找到溯源事件
確定時標(biāo)型對象
找到周圍的PPT對象
找到角色
補(bǔ)全描述對象
四色建模法案例:

領(lǐng)域建模三步曲
對于不同的人提煉出來的領(lǐng)域模型不可能完全一致,這是因為每個人對業(yè)務(wù)理解的角度都不同。那么,怎么才能保證建模的正確性?
這聽起來是個合理的質(zhì)疑,但實際上卻不是那么有道理。首先我們需要明白建模的目的是什么?如果僅僅是為了描畫問題,那么并沒有什么對錯之分——僅僅是立場和角度的差別;而如果是為了企業(yè)業(yè)務(wù)系統(tǒng)而進(jìn)行建模,那么這個問題應(yīng)該變?yōu)椋喝绾伪WC模型能夠支撐企業(yè)的運營?
我給大家梳理以下幾個步驟:
一、統(tǒng)一語言,梳理業(yè)務(wù)
在做設(shè)計的時候,梳理業(yè)務(wù)貫穿了整個過程。這需要技術(shù)與業(yè)務(wù)專家利用統(tǒng)一語言,描繪需求或問題本身,不斷梳理業(yè)務(wù),提煉出核心的領(lǐng)域模型(而非表設(shè)計)。
這有利于拉近技術(shù)人員與業(yè)務(wù)人員之間的關(guān)系,建立信任,達(dá)成統(tǒng)一的業(yè)務(wù)目標(biāo)。
二、識別聚合、聚合根
梳理完業(yè)務(wù)后,找出實體、值對象,識別出各個聚合與聚合根。
如何識別聚合與聚合根?
一個Bounded Context(限界上下文)可能包含多個聚合,每個聚合都有一個根實體,叫做聚合根;
如何進(jìn)行關(guān)聯(lián)?聚合根到聚合根:通過ID關(guān)聯(lián)聚合根到其內(nèi)部的實體,直接對象引用;聚合根到值對象,直接對象引用;對于聚合,有以下設(shè)計原則:
聚合是用來封裝真正的不變性,而不是簡單的將對象組合在一起
聚合應(yīng)盡量設(shè)計的小,盡可能小的拆分,可以避免重構(gòu),重新拆分
聚合之間的關(guān)聯(lián)通過ID,而不是對象引用
聚合內(nèi)強(qiáng)一致性,聚合之間最終一致性
應(yīng)用層實現(xiàn)跨聚合的調(diào)用,避免跨聚合的領(lǐng)域服務(wù)調(diào)用和數(shù)據(jù)表關(guān)聯(lián)。
三、劃分限界上下文
第二步完成以后,我們根據(jù)不同的場景來劃分限界上下文,以便進(jìn)行微服務(wù)拆分。通過這種基于業(yè)務(wù)理解的拆分方式,我們的系統(tǒng)就能做到高內(nèi)聚,做到單一職責(zé),拆分出來的每個微服務(wù)都是軟件變化的一個原因,不會因為某個原因發(fā)生的變更去修改每個微服務(wù),“牽一發(fā)而動全身”。

架構(gòu)設(shè)計
架構(gòu)設(shè)計作為DDD戰(zhàn)術(shù)層面的設(shè)計,關(guān)乎到設(shè)計如何落地到項目中。下面介紹跨庫關(guān)聯(lián)查詢解決方案及幾種比較流行的架構(gòu)設(shè)計方案。
跨庫關(guān)聯(lián)查詢解決方案
方案一:數(shù)據(jù)冗余
這是最簡單常用的一種解決方案——以空間換時間,把需要關(guān)聯(lián)查詢的條件冗余存儲在需要查詢的庫。舉個例子,商品與商品類目被拆到兩個獨立的服務(wù)與數(shù)據(jù)庫中,兩者通過類目編碼關(guān)聯(lián),產(chǎn)品想通過類目名稱對商品分頁查詢,這時我們可以把類目名稱冗余到商品表存儲,給它加個數(shù)據(jù)庫索引即可。
方案二:數(shù)據(jù)補(bǔ)填
結(jié)合Wrapper設(shè)計模式,一般在Dao層實現(xiàn)數(shù)據(jù)聚合——本地庫分頁查詢完數(shù)據(jù)后,通過查詢條件判斷是否需要填充關(guān)聯(lián)數(shù)據(jù),若需要則通過跨服務(wù)查詢相關(guān)聯(lián)的服務(wù),再對各個服務(wù)的數(shù)據(jù)進(jìn)行填充組裝,最后返回。
如下圖,要實現(xiàn)患者預(yù)約查詢,并聚合患者、醫(yī)生數(shù)據(jù),則在患者預(yù)約服務(wù)查詢完預(yù)約表數(shù)據(jù)后補(bǔ)填患者服務(wù)和醫(yī)生服務(wù)的數(shù)據(jù)。

這種方法的缺點就是,當(dāng)一個完整的數(shù)據(jù)涉及到N個微服務(wù),就會增加N-1個服務(wù)調(diào)用,數(shù)據(jù)全量查詢/導(dǎo)出的場景也不好使。
CQRS與Event Sourcing
CQRS(Command Query Responsibility Segregation)指的是命令查詢職責(zé)分離。Command服務(wù)專門寫數(shù)據(jù),使用關(guān)系型數(shù)據(jù)庫以保證ACID;Query服務(wù)專門讀數(shù)據(jù),一般使用NoSQL數(shù)據(jù)庫,實現(xiàn)寬表查詢,如MongoDB、ElasticSearch等。這是一種索引外置方案。

Event Sourcing事件溯源——簡單來說就是,通過事件來管理領(lǐng)域?qū)ο蟮纳芷?,事件即領(lǐng)域?qū)ο笠寻l(fā)生的事實,只增不改。一個對象從創(chuàng)建開始到消亡會經(jīng)歷很多事件,以前我們是在每次對象參與完一個業(yè)務(wù)動作后把對象的最新狀態(tài)持久化保存到數(shù)據(jù)庫中,也就是說我們的數(shù)據(jù)庫中的數(shù)據(jù)是反映了對象的當(dāng)前最新的狀態(tài)。
而事件溯源則相反,不是保存對象的最新狀態(tài),而是保存這個對象所經(jīng)歷的每個事件,所有的由對象產(chǎn)生的事件會按照時間先后順序有序的存放在數(shù)據(jù)庫中??梢钥闯?,事件溯源的這種做法是更符合事實觀的,因為它完整的描述了對象的整個生命周期過程中所經(jīng)歷的所有事件。那么,事件到底如何影響一個領(lǐng)域?qū)ο蟮臓顟B(tài)的呢?很簡單,當(dāng)我們在觸發(fā)某個領(lǐng)域?qū)ο蟮哪硞€行為時,該領(lǐng)域?qū)ο髸犬a(chǎn)生一個事件,然后該對象自己響應(yīng)該事件并更新其自己的狀態(tài),同時我們還會持久化在該對象上所發(fā)生的每一個事件;這樣當(dāng)我們要重新得到該對象的最新狀態(tài)時,只要先創(chuàng)建一個空的對象,然后將和該對象相關(guān)的所有事件按照事件發(fā)生先后順序從先到后再全部應(yīng)用一遍即可還原得到該對象的最新狀態(tài),這個過程就是所謂的事件溯源;另一方面,因為是用事件來表示對象的狀態(tài),而事件是只會增加不會修改。這就能讓數(shù)據(jù)庫里的表示對象的數(shù)據(jù)非常穩(wěn)定,不可能存在DELETE或UPDATE等操作。因為一個事件就是表示一個事實,事實是不能被磨滅或修改的。這種特性可以讓領(lǐng)域模型非常穩(wěn)定,在數(shù)據(jù)庫級別不會產(chǎn)生并發(fā)更新同一條數(shù)據(jù)的問題;
下圖為一種支持讀寫分離的演化:

寫服務(wù)只有一個統(tǒng)一的Controller入口,通過URL路徑與Body傳入進(jìn)入相應(yīng)的service、方法與入?yún)?,然后利用反射定位到具體的Service的某個方法,邏輯處理完后再分發(fā)到單Dao,通過配置Vobj.xml映射到對應(yīng)的表。這樣的好處是新增業(yè)務(wù)只需要寫Service與配置映射關(guān)系即可,不需要再新增Controller與Dao,Contoller變成了一層薄薄的接入層,大大簡化了代碼;讀服務(wù)也有統(tǒng)一的Controller,通過Service分發(fā)到不同的DAO層實現(xiàn),寫自定義的SQL或利用ES存寬表來查。
領(lǐng)域驅(qū)動架構(gòu)

與上面的單Dao實現(xiàn)類似,建立統(tǒng)一的Controller與通用的倉庫與工廠,利用數(shù)據(jù)庫第三范式實現(xiàn)服務(wù)內(nèi)數(shù)據(jù)補(bǔ)填,利用服務(wù)調(diào)用實現(xiàn)跨服務(wù)數(shù)據(jù)補(bǔ)填。
整潔架構(gòu)
整潔架構(gòu)的核心思想是通過適配器層解耦業(yè)務(wù)層與技術(shù)框架層代碼,使得業(yè)務(wù)代碼與技術(shù)框架可以各自升級迭代,互不影響。我們都知道,技術(shù)架構(gòu)一直以來都在不斷變化,對項目的技術(shù)架構(gòu)調(diào)整成本是非常高的,如何降低這種成本?
這時候整潔架構(gòu)就派上用場了

如上圖所示,中間的Entities與Use Cases屬于業(yè)務(wù)領(lǐng)域?qū)?,Entities表示業(yè)務(wù)領(lǐng)域模型的核心業(yè)務(wù),Use Cases表示與用戶交互的Service;最外層技術(shù)框架層是各種技術(shù)實現(xiàn),與業(yè)務(wù)無關(guān)的一層;那業(yè)務(wù)與技術(shù)怎么進(jìn)行關(guān)聯(lián)呢?通過中間綠色的接口適配器層實現(xiàn)。適配器層分離了技術(shù)實現(xiàn)與業(yè)務(wù)邏輯。
下圖為整潔架構(gòu)的一種落地方案。

六邊形架構(gòu)
六邊形架構(gòu)是微服務(wù)設(shè)計的基礎(chǔ)

如圖,我們把微服務(wù)封裝在六邊形里,每個微服務(wù)的核心業(yè)務(wù)是六邊形里的應(yīng)用程序與領(lǐng)域模型。與整潔架構(gòu)類似,外部接口與內(nèi)部應(yīng)用層通過各種適配器進(jìn)行關(guān)聯(lián)解耦,當(dāng)發(fā)生變更的時候,只需要修改六邊形內(nèi)部即可,不需要修改其它微服務(wù)。
清晰架構(gòu)
清晰架構(gòu)融合了DDD、整潔架構(gòu)、CQRS……曾在高水準(zhǔn)的平臺生產(chǎn)代碼中應(yīng)用,其中一個是擁有數(shù)千家遍布全球的網(wǎng)上商店的 SaaS 電子商務(wù)平臺,另一個是已經(jīng)在兩個國家上線的市場,擁有可以每月處理超過兩千萬條消息的消息總線。

如上圖清晰架構(gòu)的架構(gòu)形態(tài),左側(cè)用戶從瀏覽器/客戶端/APP等發(fā)起業(yè)務(wù),經(jīng)過左側(cè)主適配器,把不同的接入技術(shù)與應(yīng)用層解耦,這樣一來業(yè)務(wù)應(yīng)用就只需要寫一套即可,大大降低了開發(fā)成本;數(shù)據(jù)經(jīng)過應(yīng)用層后,通過右側(cè)的從適配器接入,實現(xiàn)不同的存儲形式/技術(shù)實現(xiàn),解耦業(yè)務(wù)代碼。
以上給大家介紹了DDD的基本概念、領(lǐng)域建模及幾種主流的架構(gòu)設(shè)計方案。DDD是一個非常大的課題,工程師們對DDD的各種爭論從不休止,但存在即合理,我們并不一定要把DDD落地到項目中去,在戰(zhàn)略層設(shè)計可以指導(dǎo)我們準(zhǔn)確梳理業(yè)務(wù)。
無論如何,將來想要成為業(yè)務(wù)架構(gòu)師,DDD領(lǐng)域建模與架構(gòu)設(shè)計是一堂必修課,參與到這場思想運動與實踐中是非常有必要的。
https://www.bilibili.com/video/BV1qL411u7eE?p=2&spm_id_from=pageDriver
一鍵三連,日入上千,收藏再看,年薪百萬!