導讀
introduction
最近幾年,微服務拆分大行其道,在業(yè)務越來越復雜的情況下,許多業(yè)務紛紛拋棄了傳統(tǒng)單體架構(gòu),擁抱微服務。但隨著微服務的拆分結(jié)束,大家又發(fā)現(xiàn)了新的問題,比如服務間邏輯復雜,運維復雜性變高,微服務架構(gòu)變得越來越難以管理,最終演化成大泥球架構(gòu)。
而本文主要介紹如何通過DDD對微服務進行拆分,首先介紹了什么是DDD,通過從分析DDD的優(yōu)勢,到如何通過DDD進行業(yè)務拆分,并且在最后通過代碼樣例的方式,深入淺出的為讀者介紹了DDD代碼的核心實現(xiàn)。幫助大家進一步的了解DDD應該如何落地。
全文6271字,預計閱讀時間16分鐘。
GEEK TALK
01什么是DDD
DDD(領域驅(qū)動設計),起源于2004年Eric Evans出版《領域驅(qū)動設計》,近些年由于微服務的興起,大家逐漸對單體服務進行拆分。
但是隨著微服務拆分,由于業(yè)務邏輯拆分不合理導致調(diào)用環(huán)路問題、重試風暴問題等等,都給系統(tǒng)造成了更多的風險,并且隨著業(yè)務更加復雜微服務職責劃分出現(xiàn)問題,則業(yè)務迭代效率變得越來越差,最終變成一個大泥球系統(tǒng)。
而DDD的優(yōu)勢便是指導業(yè)務進行微服務拆分,下面我們以會員中心為例來具體講解一下如何進行業(yè)務拆分以及相關(guān)的代碼實現(xiàn)。
GEEK TALK
02使用DDD的優(yōu)勢是什么
2.1語言統(tǒng)一,消除誤解
很多時候未必產(chǎn)品經(jīng)理才是最懂業(yè)務的那個人,例如某些B端服務很多時候是運營人員在向產(chǎn)品同學提需求,在經(jīng)過產(chǎn)品經(jīng)理的翻譯后,才轉(zhuǎn)化成一個需求文檔,這樣就會導致有時候產(chǎn)品經(jīng)理并不能完全表達出實際的需求,這就會導致開發(fā)人員交付的軟件無法達到預期。從而導致返工,浪費人力。
而DDD需要設計一種通用的語言,拉齊各個需求方的理解,一旦產(chǎn)品同學和技術(shù)同學對業(yè)務具備了相同的理解,統(tǒng)一的語言,那在后續(xù)的需求迭代種就會變得非常順暢。
在改造初期我們耗費了非常大的精力向產(chǎn)品同學講清楚哪些抽象應該定義為實體,實體與實體的關(guān)系是什么,在不斷的溝通、磨合中,最好我們成功建立起了一些通用的語言,拉齊了產(chǎn)品經(jīng)理、運營同學、開發(fā)人員的理解,最大幅度的消除了由于理解不一致導致的返工、重構(gòu)等工作。
2.2更專注于業(yè)務的戰(zhàn)略設計
戰(zhàn)略設計側(cè)重于業(yè)務梳理,結(jié)合業(yè)務流程劃分對應的核心域、通用域、支撐域。戰(zhàn)略設計的核心價值是圍繞產(chǎn)品規(guī)劃重點投入資源,確保重點子業(yè)務可以確保得到足夠的人力支持。
2.3設計即代碼,代碼即設計
在過去的項目詳細設計中,我們的重心在數(shù)據(jù)怎么存儲?數(shù)據(jù)流通是什么樣的。這樣可能導致在設計文檔和代碼中就具備較大的Gap,實現(xiàn)上就可能有問題。
而DDD倡導的是思考,而不是寫代碼。在代碼設計之前定義好領域語言,和領域?qū)<覝贤o礙,定義好領域規(guī)則,這樣在寫代碼的時候留下較少的思考。代碼只是把設計文檔翻譯成代碼,寫代碼更像是在照著設計文檔在做填空題,只需要將代碼填到指定的文件中即可。
GEEK TALK
03如何使用DDD
3.1DDD戰(zhàn)略設計
3.1.1劃分核心域,通用域、支撐域
在實際的工作中,很多產(chǎn)品經(jīng)理會陷入到各種繁雜的業(yè)務指標中,無法從繁雜的業(yè)務中抽身,定義好哪些是重要的模塊,或者無法表達出業(yè)務各個模塊中最重要的是什么。這種情況就會導致每個,產(chǎn)品沒有這就會導致在人員分工、資源申請上出現(xiàn)一些問題。
做戰(zhàn)略設計,最核心的事情就是劃分清楚核心域,通用域、支撐域,我們把更多的精力投入到核心的問題中,而不被大量次要的問題淹沒。
核心域:業(yè)務最核心的部分,這部分需要產(chǎn)品同學確定,例如,從長線來看我們主要核心做的投入,是做流量引入,還是做變現(xiàn)
支撐域:業(yè)務中非核心的部分,若產(chǎn)品確定現(xiàn)有核心域是流量引入,那在流量變現(xiàn)部分業(yè)務,就是支撐域
通用域:例如登錄驗證、驗證碼、支付能力等則更多的使用公司內(nèi)部的中臺能力,若公司沒有通用的中臺能力,我們也會以建設中臺的思路自建一個內(nèi)部的中臺服務
本處僅僅描述我們對于戰(zhàn)略設計理解,不對戰(zhàn)略設計展開說明。
3.1.2劃分邊界
微服務職責的劃分是執(zhí)行環(huán)節(jié)的第一步,也是最重要的一步,尤其從大單體拆分為多個微服務時,需要考慮以下幾點
要通過領域驅(qū)動劃分邊界,若暫時考慮不清楚邊界,那就先不要拆分
明確微服務分層,上游服務只能對下游服務產(chǎn)生依賴,防止微服務環(huán)路調(diào)用問題,同時下游服務需要考慮重試風暴問題
核心域的微服務需要具備故障降級,容災能力
要基于組織架構(gòu)進行邊界的劃分,微服務的梳理其實也是團隊的梳理,過度的拆分可能導致更多的溝通成本
3.2DDD戰(zhàn)術(shù)設計
3.2.1 名詞解釋
聚合與聚合根:是一組相關(guān)對象的組合,可以作為拆分微服務的最小單位,具有高內(nèi)聚、低耦合的特點,聚合在DDD中是一個很重要的概念,核心領域往往都需要用聚合來表達;聚合根為其根節(jié)點,聚合根有實體的特點,具有全局唯一標識,有獨立的生命周期。一個聚合只有一個聚合根,聚合根在聚合內(nèi)對實體和值對象采用直接對象引用的方式進行組織和協(xié)調(diào),聚合根與聚合根之間通過 ID 關(guān)聯(lián)的方式實現(xiàn)聚合之間的協(xié)同。
領域服務:一些重要的領域行為或操作,可以歸類為領域服務。它既不是實體,也不是值對象的范疇。
領域事件:領域事件是對領域內(nèi)發(fā)生的活動進行的建模。
實體:多個屬性、行為及操作的載體,實體有全局唯一性標識(ID),有獨立的生命周期。例如會員用戶中,每個會員都可以被認為一個實體,都有userid唯一性標識。
值對象:通過對象屬性來識別的對象,沒有標識符概念,無生命周期,只描述業(yè)務屬性。如在一個會員系統(tǒng)中,會員權(quán)益信息集合即可看為一個值對象,只用于對權(quán)益屬性的描述,只有數(shù)據(jù)初始化操作和有限的不涉及修改數(shù)據(jù)的行為。
3.2.2 如何進行戰(zhàn)術(shù)設計
接下來我們以會員中心為例為大家詳細介紹
在戰(zhàn)略模型中我們已經(jīng)劃分清楚邊界,梳理不同領域及相關(guān)關(guān)系。接下來我們需要從戰(zhàn)術(shù)層面上剖析領域模型內(nèi)部之間的關(guān)系,對會員上下文進行建模(下文為簡化版)。
在會員上下文中,我們以會員實體為中心,通過會員(vipinfo)這個聚合根來控制會員權(quán)限,一個會員包括用戶ID(uid)、會員權(quán)益(Privilege)、所屬機構(gòu)(tp)以及會員碼(vip_code),而會員碼針對訂單維度分別對應不同的權(quán)益內(nèi)容(privilege)。
這些值對象不具有業(yè)務行為特征,只關(guān)心本身屬性值。會員實體具有業(yè)務行為及業(yè)務邏輯,例如會員入駐、會員變更、會員綁碼等,外部訪問會員權(quán)益值對象等都需要通過會員實體來進行。
在會員域中,我們同時支持會員碼及會員維度的領域服務,包括購買、獲取會員信息、綁碼等服務。
3.3 DDD代碼實現(xiàn)
3.3.1 項目介紹
會員微服務主要實現(xiàn)獲得會員信息、會員碼信息、綁會員碼、會員碼退款等操作。
服務使用ddd四層架構(gòu),分為接口層、應用層、領域?qū)雍突A層。
本服務因為業(yè)務復雜性較低,為減少冗余代碼,使用松散分層。(架構(gòu)根據(jù)耦合的緊密程度又可以分為兩種:嚴格分層架構(gòu)和松散分層架構(gòu)。嚴格分層:任何層只能依賴與他相鄰的下層。松散分層:任何層可以依賴任意他的下層。)
3.3.2 項目結(jié)構(gòu)
項目結(jié)構(gòu)如圖所示分為四層、對應到到代碼目錄上(附錄1),代碼一級目錄有interface(接口層)、application(應用層)、domain(領域?qū)樱?、infrastructure(基礎層)四個目錄。
接口層:
接口層處理接口定義、批處理相關(guān)邏輯。目錄如下:
|-- interface | |-- command // 批處理接口層 | | |-- controller | | | `-- vip | | | |-- add.go | | | `-- update.go | | |-- router.go // 代碼入口定義 | | `-- script.go | `-- http // api接口層 | |-- controller // 接口入?yún)⑿r?、定義,調(diào)用下層代碼 | | |-- lawyer | | | |-- add.go | | | `-- update.go | | `-- vipcode | | |-- add.go | | `-- update.go | |-- router.go // api路由
?
interface目錄下有command、http兩個目錄,其中,
command:包含批處理入口,批處理路由,編排批處理相關(guān)領域?qū)臃铡⑹录?、實體和基礎層相關(guān)函數(shù)。批處理代碼無需應用層直接依賴領域?qū)?、基礎層,降低代碼冗余度。
http:包含接口路由、定義,接口入?yún)⑿r?、定義。
應用層:
主要負責組織、編排領域?qū)臃?、事件、實體和基礎層相關(guān)函數(shù)。
application下有service、viewmodel。
|--application | |-- service //應用層服務 | | |-- lawyer | | | |-- add.go | | | `-- update.go | | `-- vip | | |-- add.go | | `-- update.go | `-- viewmodel // 視圖 | |-- lawyer | | |-- transform.go // 轉(zhuǎn)化函數(shù) | | `-- vm.go //視圖數(shù)據(jù)結(jié)構(gòu) | `-- vip | |-- transform.go | `-- vm.go
service:對多個領域服務、基礎層ral調(diào)用、數(shù)據(jù)持久化服務進行封裝、編排,為上層提供更粗粒度的服務,調(diào)用領域?qū)臃眨瑐}儲和事件,因為松散分層結(jié)構(gòu),也可以調(diào)用基礎層服務。
viewmodel:為上層多變的數(shù)據(jù)結(jié)構(gòu)要求,提供相應視圖定義和實體到視圖的轉(zhuǎn)化方法。
領域?qū)樱?/strong>
領域?qū)哟娣艠I(yè)務核心邏輯包括聚合根、實體、值對象、倉儲接口、領域服務、領域事件接口等。
領域?qū)酉路钟形鍌€目錄:
|--domain | |-- aggregate // 聚合 | | |-- lawyer | | | |-- entity.go // 實體定義 | | | `-- vo.go // 值對象 | | `-- vipcode | | |-- entity.go | | |-- vo.go | |-- event // 領域事件 | | `-- vipcode | | `-- order.go | |-- repository // 倉儲接口 | | |-- lawyer.go | | `-- vipcode.go | |-- adaptor // 防腐層 | | `-- sms.go | `-- service // 領域服務 | |-- lawyer | | `-- vipcode.go | `-- vipcode | `-- vipcode.go
aggregate:放置聚合根,實體、值對象數(shù)據(jù)結(jié)構(gòu)定義,以及相關(guān)初始化代碼。
領域內(nèi)數(shù)據(jù)流轉(zhuǎn)處理依賴,相關(guān)聚合根,下游服務發(fā)生改變——如數(shù)據(jù)表結(jié)構(gòu)變換,只需將相關(guān)數(shù)據(jù)轉(zhuǎn)化為業(yè)務定義聚合根,代碼更改只需在基礎層,不涉及上層。
下面是會員碼實體示例,里面又包含有訂單值對象,會員碼機構(gòu)值對象和會員碼權(quán)益值對象。
// EntityVipCode 會員碼實體(簡化版本) type EntityVipCode struct { ValidityStart *time.Time // 綁碼開始時間 ValidityEnd *time.Time // 綁定會員碼結(jié)束時間 OrderInfo *VOOrderInfo // 訂單信息值對象 BuyerInfo *VOCodeBuyerInfo // 買會員碼機構(gòu)信息 PrivilegeInfo *VOPrivilege // 會員碼包含的權(quán)益 }
event:放置基礎層事件抽象的接口——為了實現(xiàn)依賴倒置。
repository:放置基礎層數(shù)據(jù)持久化服務抽象的接口。
service:存放一下領域服務代碼,向應用層服務提供方法調(diào)用,依賴倒置在ddd中使用頻繁 。
adaptor:存放防腐層數(shù)據(jù)結(jié)構(gòu)定義、轉(zhuǎn)化函數(shù)。
防腐層在下游服務和上游服務之間,將下游服務翻譯為上游服務語言,拋去無需關(guān)注的,防止上層服務摻雜過多無需關(guān)注雜質(zhì)。
ddd中廣泛應用了依賴倒置原則(即調(diào)用要依賴于抽象接口,不要依賴于具體實現(xiàn)),減少ddd各層之間的耦合性,提高系統(tǒng)的穩(wěn)定性,減少并行開發(fā)風險,提高代碼的可讀性和可維護性,非常適合ddd這樣為應對頻繁迭代的設計思想。
如下創(chuàng)建訂單體現(xiàn)依賴倒置思想,無需關(guān)注具體實現(xiàn),假若使用訂單方發(fā)生了變更(如更換服務提供方、服務提供方更換實現(xiàn)邏輯或者服務實現(xiàn)邏輯更改了),我們在上層代碼只需要更改傳入的參數(shù),無需關(guān)注其他變更。
// ReqCreateOrder 創(chuàng)建訂單 func ReqCreateOrder(ctx context.Context, vipRepo repository.IVipCodeRepo, vipcodeentity vipcode.EntityVipCode) (*order.PreorderRetData, error) type IVipCodeRepo interface { CreateOrder(ctx context.Context, ev vipcode.EntityVipCode) (*liborder.PreorderRetData, error) UpdateVipCode(ctx context.Context, patch map[string]interface{}, conditions map[string]interface{}) (int64, error) }
基礎設施層:
基礎層存放領域事件、數(shù)據(jù)持久化、ral調(diào)用相關(guān)代碼。
其下有三個目錄:
|-- infrastructure | |-- event // 領域事件實現(xiàn) | | |-- init.go | | `-- vipcode | | `-- consume_order.go | |-- persistence // 持久化存儲實現(xiàn) | | |-- init.go | | |-- lawyer | | | |-- po.go | | | |-- repo.go | | | `-- transform.go | | `-- vipcode | | |-- po.go | | |-- repo.go | | `-- transform.go | `-- rpc // rpc調(diào)用實現(xiàn) | |-- db | |-- init.go | |-- redis
event:領域事件具體實現(xiàn),依賴rpc服務。
persistence:放置儲存持久化代碼,數(shù)據(jù)庫存儲對應PO,和PO到實體的轉(zhuǎn)化方法,所有具體實現(xiàn)方法都出參都需要轉(zhuǎn)化成實體供給上層使用。
rpc:rpc遠程調(diào)用其他微服務、消息中間件等服務代碼。
GEEK TALK
04總結(jié)
用好DDD的關(guān)鍵,就是理解DDD和核心思想,其本質(zhì)也是面向?qū)ο蟮脑O計方法,即是把業(yè)務模型轉(zhuǎn)換為對象模型從而來控制業(yè)務持續(xù)變化而導致系統(tǒng)的復雜性,使得系統(tǒng)更加具有可擴展性、可維護性。
在相對比較小、邏輯簡單的微服務,在代碼實現(xiàn)層面,我們并沒有按照DDD進行開發(fā),傳統(tǒng)的MVC足以應對,若強行使用DDD則會徒增大家的工作量。
DDD的核心是通過戰(zhàn)略設計來匹配產(chǎn)品層面的業(yè)務規(guī)劃,在戰(zhàn)術(shù)設計層面通過對每個模塊進行抽象、建模來完成業(yè)務梳理劃分邊界,在代碼實現(xiàn)層面來完成設計文檔到代碼的映射,做到設計即代碼、代碼即設計。
而DDD只適用于大型的、復雜的業(yè)務場景。切勿為了DDD而DDD。
審核編輯:湯梓紅
-
編程
+關(guān)注
關(guān)注
88文章
3640瀏覽量
94036 -
代碼
+關(guān)注
關(guān)注
30文章
4837瀏覽量
69128 -
ddd
+關(guān)注
關(guān)注
0文章
23瀏覽量
2962 -
微服務
+關(guān)注
關(guān)注
0文章
143瀏覽量
7443
原文標題:深入淺出DDD編程
文章出處:【微信號:OSC開源社區(qū),微信公眾號:OSC開源社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
深入淺出matlab
![<b class='flag-5'>深入淺出</b>matlab](https://file.elecfans.com/web2/M00/48/78/pYYBAGKhtAiADQGbAAAri-e95oY348.jpg)
評論