懟不過(guò)產(chǎn)品經(jīng)理?因?yàn)槟悴欢瓺DD領(lǐng)域建模與架構(gòu)設(shè)計(jì)

前幾年就開(kāi)始接觸DDD(Domain Driven Design,領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)),并且著迷于此。它更多地在戰(zhàn)略層指導(dǎo)了我的設(shè)計(jì),對(duì)于戰(zhàn)術(shù)層面的設(shè)計(jì),目前業(yè)界沒(méi)有統(tǒng)一的標(biāo)準(zhǔn),也沒(méi)有特別流行的方案。雖然也有許多技術(shù)大牛們熱衷于DDD,但一到代碼落地便一地雞毛,造不出“銀彈”。

那DDD到底是什么呢?有什么技術(shù)落地方案呢?今天我來(lái)給大家科普一下。

基本概念

過(guò)去系統(tǒng)分析和系統(tǒng)設(shè)計(jì)都是分離的,正如我們國(guó)家“系統(tǒng)分析師” 和“系統(tǒng)設(shè)計(jì)師” 兩種職稱考試一樣,這樣割裂的結(jié)果導(dǎo)致,需求分析的結(jié)果無(wú)法直接進(jìn)行設(shè)計(jì)編程,而能夠進(jìn)行編程運(yùn)行的代碼卻扭曲需求,導(dǎo)致客戶運(yùn)行軟件后才發(fā)現(xiàn)很多功能不是自己想要的,而且軟件不能快速跟隨需求變化。

DDD則打破了這種隔閡,提出了領(lǐng)域模型概念,統(tǒng)一了分析和設(shè)計(jì)編程,使得軟件能夠更靈活快速跟隨需求變化。

DDD專門(mén)為解決復(fù)雜性而誕生,因此解決思路完全不同于傳統(tǒng)的CRUD,但是DDD本身掌握起來(lái)并不會(huì)感覺(jué)復(fù)雜,從程序員角度看,DDD其實(shí)是研究將包含業(yè)務(wù)邏輯的ifelse語(yǔ)句放在哪里的學(xué)問(wèn)。

Q:DDD有什么作用?

A:想想我們做軟件設(shè)計(jì)的初衷是什么?通過(guò)微服務(wù)與領(lǐng)域驅(qū)動(dòng)設(shè)計(jì),簡(jiǎn)化設(shè)計(jì),降低維護(hù)成本,提高軟件交付速度。這要求我們落地的時(shí)候,架設(shè)一個(gè)支持微服務(wù)、支持領(lǐng)域驅(qū)動(dòng)的架構(gòu)。

Q:DDD適用于什么場(chǎng)景?

A:在Evans寫(xiě)的《領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)》一書(shū),副標(biāo)題已經(jīng)說(shuō)明一切:軟件核心復(fù)雜性應(yīng)對(duì)之道。領(lǐng)域驅(qū)動(dòng)的真正作用,在于項(xiàng)目中對(duì)日后的維護(hù),當(dāng)系統(tǒng)變得越來(lái)越復(fù)雜的時(shí)候,才能體現(xiàn)出它的威力。

那新項(xiàng)目該不該用領(lǐng)域驅(qū)動(dòng)?新項(xiàng)目并不復(fù)雜,產(chǎn)品還在跑模式的階段,雖然它的優(yōu)勢(shì)并不能真正發(fā)揮出來(lái),但我認(rèn)為,DDD同樣適用新項(xiàng)目。項(xiàng)目只會(huì)越做越復(fù)雜,我們從一開(kāi)始就應(yīng)該考慮日漸發(fā)胖的代碼、隨時(shí)可能獨(dú)立的子業(yè)務(wù)。而新項(xiàng)目更多的是指導(dǎo)戰(zhàn)略層設(shè)計(jì)(如領(lǐng)域建模),戰(zhàn)術(shù)層的技術(shù)落地還是以團(tuán)隊(duì)成員最熟悉的方式進(jìn)行,目標(biāo)是持續(xù)快速交付、降低維護(hù)成本。

領(lǐng)域建模

Q:什么是領(lǐng)域模型?

A:為解決場(chǎng)景下的問(wèn)題而形成的一套模型,然后使用這套模型來(lái)解決業(yè)務(wù)問(wèn)題。 根據(jù)重復(fù)勞動(dòng)經(jīng)驗(yàn)我們會(huì)形成一套模式,領(lǐng)域模型也一樣會(huì)形成一套模式,包括:實(shí)體、值對(duì)象、模塊、領(lǐng)域服務(wù)。

領(lǐng)域發(fā)現(xiàn)

那領(lǐng)域模型是怎么一步一步確定下來(lái)的呢?推薦兩種比較常用的領(lǐng)域發(fā)現(xiàn)方法:事件風(fēng)暴與四色建模法。

一、事件風(fēng)暴

Event Storming(事件風(fēng)暴)是一種輕量級(jí)的系統(tǒng)分析方法,基于 DDD 的概念,能夠?yàn)槲覀兪崂硐到y(tǒng)中的各種相關(guān)元素,其中包括了核心的 Aggregate。它能夠幫助開(kāi)發(fā)人員梳理核心的業(yè)務(wù)對(duì)象,從某種程度上來(lái)說(shuō)就是就是領(lǐng)域?qū)ο笾械木酆稀?/p>

描述產(chǎn)品愿景

產(chǎn)品愿景的主要目的是對(duì)產(chǎn)品頂層價(jià)值的設(shè)計(jì),使產(chǎn)品目標(biāo)用戶、核心價(jià)值、差異化競(jìng)爭(zhēng)點(diǎn)等信息達(dá)成一致,避免產(chǎn)品偏離方向。

產(chǎn)品愿景的參與角色:領(lǐng)域?qū)<摇I(yè)務(wù)需求方、產(chǎn)品經(jīng)理、項(xiàng)目經(jīng)理和開(kāi)發(fā)經(jīng)理。

事件風(fēng)暴關(guān)注點(diǎn)

事件:某個(gè)動(dòng)作的結(jié)果

屬性:事件的輸入、輸出

命令:某個(gè)動(dòng)作

實(shí)體:命令的觸發(fā)者

簡(jiǎn)單理解就是誰(shuí)(實(shí)體)使用什么(輸入)做了什么(命令、動(dòng)作)產(chǎn)生了什么(輸出)影響了什么(事件)。

二、四色建模法

通過(guò)還原業(yè)務(wù)邏輯事件,依據(jù)是否影響公司的運(yùn)營(yíng)和發(fā)展,確定憑證作為時(shí)標(biāo)型對(duì)象,并補(bǔ)全相關(guān)描述的建模方法。四色建模法一般運(yùn)用于問(wèn)題分析建模。

四色建模是哪四色?它包括——

時(shí)標(biāo)型(Moment-Interval)對(duì)象:具有可追溯性的記錄運(yùn)營(yíng)或管理數(shù)據(jù)的時(shí)刻或時(shí)段對(duì)象,用粉紅色表示;

PPT(Party/Place/Thing)對(duì)象:代表參與到流程中的參與方/地點(diǎn)/物,用綠色表示;

角色(Role)對(duì)象:在時(shí)標(biāo)型對(duì)象與 PPT 對(duì)象(通常是參與方)之間參與的角色,用黃色表示;

描述(Description)對(duì)象:對(duì) PPT 對(duì)象的一種補(bǔ)充描述,用藍(lán)色表示。

分析的五個(gè)步驟

  1. 找到溯源事件

  2. 確定時(shí)標(biāo)型對(duì)象

  3. 找到周?chē)腜PT對(duì)象

  4. 找到角色

  5. 補(bǔ)全描述對(duì)象

四色建模法案例:

領(lǐng)域建模三步曲

對(duì)于不同的人提煉出來(lái)的領(lǐng)域模型不可能完全一致,這是因?yàn)槊總€(gè)人對(duì)業(yè)務(wù)理解的角度都不同。那么,怎么才能保證建模的正確性?

這聽(tīng)起來(lái)是個(gè)合理的質(zhì)疑,但實(shí)際上卻不是那么有道理。首先我們需要明白建模的目的是什么?如果僅僅是為了描畫(huà)問(wèn)題,那么并沒(méi)有什么對(duì)錯(cuò)之分——僅僅是立場(chǎng)和角度的差別;而如果是為了企業(yè)業(yè)務(wù)系統(tǒng)而進(jìn)行建模,那么這個(gè)問(wèn)題應(yīng)該變?yōu)椋喝绾伪WC模型能夠支撐企業(yè)的運(yùn)營(yíng)?

我給大家梳理以下幾個(gè)步驟:

一、統(tǒng)一語(yǔ)言,梳理業(yè)務(wù)

在做設(shè)計(jì)的時(shí)候,梳理業(yè)務(wù)貫穿了整個(gè)過(guò)程。這需要技術(shù)與業(yè)務(wù)專家利用統(tǒng)一語(yǔ)言,描繪需求或問(wèn)題本身,不斷梳理業(yè)務(wù),提煉出核心的領(lǐng)域模型(而非表設(shè)計(jì))。

這有利于拉近技術(shù)人員與業(yè)務(wù)人員之間的關(guān)系,建立信任,達(dá)成統(tǒng)一的業(yè)務(wù)目標(biāo)。

二、識(shí)別聚合、聚合根

梳理完業(yè)務(wù)后,找出實(shí)體、值對(duì)象,識(shí)別出各個(gè)聚合與聚合根。

如何識(shí)別聚合與聚合根?

一個(gè)Bounded Context(限界上下文)可能包含多個(gè)聚合,每個(gè)聚合都有一個(gè)根實(shí)體,叫做聚合根;

如何進(jìn)行關(guān)聯(lián)?聚合根到聚合根:通過(guò)ID關(guān)聯(lián)聚合根到其內(nèi)部的實(shí)體,直接對(duì)象引用;聚合根到值對(duì)象,直接對(duì)象引用;對(duì)于聚合,有以下設(shè)計(jì)原則:

  1. 聚合是用來(lái)封裝真正的不變性,而不是簡(jiǎn)單的將對(duì)象組合在一起

  2. 聚合應(yīng)盡量設(shè)計(jì)的小,盡可能小的拆分,可以避免重構(gòu),重新拆分

  3. 聚合之間的關(guān)聯(lián)通過(guò)ID,而不是對(duì)象引用

  4. 聚合內(nèi)強(qiáng)一致性,聚合之間最終一致性

應(yīng)用層實(shí)現(xiàn)跨聚合的調(diào)用,避免跨聚合的領(lǐng)域服務(wù)調(diào)用和數(shù)據(jù)表關(guān)聯(lián)。

三、劃分限界上下文

第二步完成以后,我們根據(jù)不同的場(chǎng)景來(lái)劃分限界上下文,以便進(jìn)行微服務(wù)拆分。通過(guò)這種基于業(yè)務(wù)理解的拆分方式,我們的系統(tǒng)就能做到高內(nèi)聚,做到單一職責(zé),拆分出來(lái)的每個(gè)微服務(wù)都是軟件變化的一個(gè)原因,不會(huì)因?yàn)槟硞€(gè)原因發(fā)生的變更去修改每個(gè)微服務(wù),“牽一發(fā)而動(dòng)全身”。

架構(gòu)設(shè)計(jì)

架構(gòu)設(shè)計(jì)作為DDD戰(zhàn)術(shù)層面的設(shè)計(jì),關(guān)乎到設(shè)計(jì)如何落地到項(xiàng)目中。下面介紹跨庫(kù)關(guān)聯(lián)查詢解決方案及幾種比較流行的架構(gòu)設(shè)計(jì)方案。

跨庫(kù)關(guān)聯(lián)查詢解決方案

方案一:數(shù)據(jù)冗余

這是最簡(jiǎn)單常用的一種解決方案——以空間換時(shí)間,把需要關(guān)聯(lián)查詢的條件冗余存儲(chǔ)在需要查詢的庫(kù)。舉個(gè)例子,商品與商品類(lèi)目被拆到兩個(gè)獨(dú)立的服務(wù)與數(shù)據(jù)庫(kù)中,兩者通過(guò)類(lèi)目編碼關(guān)聯(lián),產(chǎn)品想通過(guò)類(lèi)目名稱對(duì)商品分頁(yè)查詢,這時(shí)我們可以把類(lèi)目名稱冗余到商品表存儲(chǔ),給它加個(gè)數(shù)據(jù)庫(kù)索引即可。

方案二:數(shù)據(jù)補(bǔ)填

結(jié)合Wrapper設(shè)計(jì)模式,一般在Dao層實(shí)現(xiàn)數(shù)據(jù)聚合——本地庫(kù)分頁(yè)查詢完數(shù)據(jù)后,通過(guò)查詢條件判斷是否需要填充關(guān)聯(lián)數(shù)據(jù),若需要?jiǎng)t通過(guò)跨服務(wù)查詢相關(guān)聯(lián)的服務(wù),再對(duì)各個(gè)服務(wù)的數(shù)據(jù)進(jìn)行填充組裝,最后返回。

如下圖,要實(shí)現(xiàn)患者預(yù)約查詢,并聚合患者、醫(yī)生數(shù)據(jù),則在患者預(yù)約服務(wù)查詢完預(yù)約表數(shù)據(jù)后補(bǔ)填患者服務(wù)和醫(yī)生服務(wù)的數(shù)據(jù)。

這種方法的缺點(diǎn)就是,當(dāng)一個(gè)完整的數(shù)據(jù)涉及到N個(gè)微服務(wù),就會(huì)增加N-1個(gè)服務(wù)調(diào)用,數(shù)據(jù)全量查詢/導(dǎo)出的場(chǎng)景也不好使。

CQRS與Event Sourcing

CQRS(Command Query Responsibility Segregation)指的是命令查詢職責(zé)分離。Command服務(wù)專門(mén)寫(xiě)數(shù)據(jù),使用關(guān)系型數(shù)據(jù)庫(kù)以保證ACID;Query服務(wù)專門(mén)讀數(shù)據(jù),一般使用NoSQL數(shù)據(jù)庫(kù),實(shí)現(xiàn)寬表查詢,如MongoDB、ElasticSearch等。這是一種索引外置方案。

Event Sourcing事件溯源——簡(jiǎn)單來(lái)說(shuō)就是,通過(guò)事件來(lái)管理領(lǐng)域?qū)ο蟮纳芷冢录搭I(lǐng)域?qū)ο笠寻l(fā)生的事實(shí),只增不改。一個(gè)對(duì)象從創(chuàng)建開(kāi)始到消亡會(huì)經(jīng)歷很多事件,以前我們是在每次對(duì)象參與完一個(gè)業(yè)務(wù)動(dòng)作后把對(duì)象的最新?tīng)顟B(tài)持久化保存到數(shù)據(jù)庫(kù)中,也就是說(shuō)我們的數(shù)據(jù)庫(kù)中的數(shù)據(jù)是反映了對(duì)象的當(dāng)前最新的狀態(tài)。

而事件溯源則相反,不是保存對(duì)象的最新?tīng)顟B(tài),而是保存這個(gè)對(duì)象所經(jīng)歷的每個(gè)事件,所有的由對(duì)象產(chǎn)生的事件會(huì)按照時(shí)間先后順序有序的存放在數(shù)據(jù)庫(kù)中??梢钥闯?,事件溯源的這種做法是更符合事實(shí)觀的,因?yàn)樗暾拿枋隽藢?duì)象的整個(gè)生命周期過(guò)程中所經(jīng)歷的所有事件。那么,事件到底如何影響一個(gè)領(lǐng)域?qū)ο蟮臓顟B(tài)的呢?很簡(jiǎn)單,當(dāng)我們?cè)谟|發(fā)某個(gè)領(lǐng)域?qū)ο蟮哪硞€(gè)行為時(shí),該領(lǐng)域?qū)ο髸?huì)先產(chǎn)生一個(gè)事件,然后該對(duì)象自己響應(yīng)該事件并更新其自己的狀態(tài),同時(shí)我們還會(huì)持久化在該對(duì)象上所發(fā)生的每一個(gè)事件;這樣當(dāng)我們要重新得到該對(duì)象的最新?tīng)顟B(tài)時(shí),只要先創(chuàng)建一個(gè)空的對(duì)象,然后將和該對(duì)象相關(guān)的所有事件按照事件發(fā)生先后順序從先到后再全部應(yīng)用一遍即可還原得到該對(duì)象的最新?tīng)顟B(tài),這個(gè)過(guò)程就是所謂的事件溯源;另一方面,因?yàn)槭怯檬录?lái)表示對(duì)象的狀態(tài),而事件是只會(huì)增加不會(huì)修改。這就能讓數(shù)據(jù)庫(kù)里的表示對(duì)象的數(shù)據(jù)非常穩(wěn)定,不可能存在DELETE或UPDATE等操作。因?yàn)橐粋€(gè)事件就是表示一個(gè)事實(shí),事實(shí)是不能被磨滅或修改的。這種特性可以讓領(lǐng)域模型非常穩(wěn)定,在數(shù)據(jù)庫(kù)級(jí)別不會(huì)產(chǎn)生并發(fā)更新同一條數(shù)據(jù)的問(wèn)題;

下圖為一種支持讀寫(xiě)分離的演化:

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

領(lǐng)域驅(qū)動(dòng)架構(gòu)

與上面的單Dao實(shí)現(xiàn)類(lèi)似,建立統(tǒng)一的Controller與通用的倉(cāng)庫(kù)與工廠,利用數(shù)據(jù)庫(kù)第三范式實(shí)現(xiàn)服務(wù)內(nèi)數(shù)據(jù)補(bǔ)填,利用服務(wù)調(diào)用實(shí)現(xiàn)跨服務(wù)數(shù)據(jù)補(bǔ)填。

整潔架構(gòu)

整潔架構(gòu)的核心思想是通過(guò)適配器層解耦業(yè)務(wù)層與技術(shù)框架層代碼,使得業(yè)務(wù)代碼與技術(shù)框架可以各自升級(jí)迭代,互不影響。我們都知道,技術(shù)架構(gòu)一直以來(lái)都在不斷變化,對(duì)項(xiàng)目的技術(shù)架構(gòu)調(diào)整成本是非常高的,如何降低這種成本?

這時(shí)候整潔架構(gòu)就派上用場(chǎng)了。

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

下圖為整潔架構(gòu)的一種落地方案。

六邊形架構(gòu)

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

如圖,我們把微服務(wù)封裝在六邊形里,每個(gè)微服務(wù)的核心業(yè)務(wù)是六邊形里的應(yīng)用程序與領(lǐng)域模型。與整潔架構(gòu)類(lèi)似,外部接口與內(nèi)部應(yīng)用層通過(guò)各種適配器進(jìn)行關(guān)聯(lián)解耦,當(dāng)發(fā)生變更的時(shí)候,只需要修改六邊形內(nèi)部即可,不需要修改其它微服務(wù)。

清晰架構(gòu)

清晰架構(gòu)融合了DDD、整潔架構(gòu)、CQRS……曾在高水準(zhǔn)的平臺(tái)生產(chǎn)代碼中應(yīng)用,其中一個(gè)是擁有數(shù)千家遍布全球的網(wǎng)上商店的 SaaS 電子商務(wù)平臺(tái),另一個(gè)是已經(jīng)在兩個(gè)國(guó)家上線的市場(chǎng),擁有可以每月處理超過(guò)兩千萬(wàn)條消息的消息總線。

如上圖清晰架構(gòu)的架構(gòu)形態(tài),左側(cè)用戶從瀏覽器/客戶端/APP等發(fā)起業(yè)務(wù),經(jīng)過(guò)左側(cè)主適配器,把不同的接入技術(shù)與應(yīng)用層解耦,這樣一來(lái)業(yè)務(wù)應(yīng)用就只需要寫(xiě)一套即可,大大降低了開(kāi)發(fā)成本;數(shù)據(jù)經(jīng)過(guò)應(yīng)用層后,通過(guò)右側(cè)的從適配器接入,實(shí)現(xiàn)不同的存儲(chǔ)形式/技術(shù)實(shí)現(xiàn),解耦業(yè)務(wù)代碼。

以上給大家介紹了DDD的基本概念、領(lǐng)域建模及幾種主流的架構(gòu)設(shè)計(jì)方案。DDD是一個(gè)非常大的課題,工程師們對(duì)DDD的各種爭(zhēng)論從不休止,但存在即合理,我們并不一定要把DDD落地到項(xiàng)目中去,在戰(zhàn)略層設(shè)計(jì)可以指導(dǎo)我們準(zhǔn)確梳理業(yè)務(wù)。

無(wú)論如何,將來(lái)想要成為業(yè)務(wù)架構(gòu)師,DDD領(lǐng)域建模與架構(gòu)設(shè)計(jì)是一堂必修課,參與到這場(chǎng)思想運(yùn)動(dòng)與實(shí)踐中是非常有必要的。

老規(guī)矩,一鍵三連,日入上千,點(diǎn)贊在看,年薪百萬(wàn)!

本文作者:Jensen
7年Java老兵,小米主題設(shè)計(jì)師,手機(jī)輸入法設(shè)計(jì)師,ProcessOn特邀講師。
曾涉獵航空、電信、IoT、垂直電商產(chǎn)品研發(fā),現(xiàn)就職于某知名電商企業(yè)。
技術(shù)公眾號(hào)【架構(gòu)師修行錄】號(hào)主,專注于分享程序員日常、架構(gòu)技術(shù)、職場(chǎng)干貨,關(guān)注回復(fù)“DDD”領(lǐng)取DDD領(lǐng)域建模資料,回復(fù)“職場(chǎng)”領(lǐng)取升職加薪資料。
交個(gè)朋友,一起成長(zhǎng)!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容