怎么說服領(lǐng)導(dǎo),能讓我用DDD架構(gòu)?
本文轉(zhuǎn)載自微信公眾號(hào)「bugstack蟲洞棧」,作者小傅哥。轉(zhuǎn)載本文請(qǐng)聯(lián)系bugstack蟲洞棧公眾號(hào)。
一、前言
領(lǐng)導(dǎo):為什么要使用DDD?
我也苦思冥想,怎么跟領(lǐng)導(dǎo)說咱們從 MVC 升級(jí)到 DDD 吧,因?yàn)?DDD 代碼結(jié)構(gòu)更加清晰、領(lǐng)域驅(qū)動(dòng)比測(cè)試驅(qū)動(dòng)開發(fā)更加先進(jìn)、研發(fā)的兄弟們也更想用用新框架等。
不過這么聊被噴一頓不說,還得說你是過度設(shè)計(jì)瞎折騰,咋回事呢?因?yàn)闆]聊到重點(diǎn)呀,你MVC升級(jí)DDD;給業(yè)務(wù)帶來了什么、提升了交付效率嗎、降低了公司研發(fā)成本嗎,都沒有?不僅沒有,你還說為了后期的迭代維護(hù),前期會(huì)需要更多的設(shè)計(jì)和開發(fā)時(shí)間。咋?你是想這一個(gè)Q就把我送走嗎,我剛來咱們部門KPI在那懸著,壓的我頭發(fā)都白了!別瞎搞,求穩(wěn)!
那就不搞了嗎?搞哇,不讓搞換領(lǐng)導(dǎo)!但搞之前,要考慮清楚,DDD 不是 Silver Bullet,你有一腔熱血雖好,可是也得知曉 DDD 的設(shè)計(jì)原則是什么、它更適合的場(chǎng)景是什么、與 MVC 對(duì)比有什么云泥之別。
二、開發(fā)成本
使用 DDD 模式開發(fā)代碼的成本到底在哪?是因?yàn)槭褂?DDD 四層分層結(jié)構(gòu)就比MVC 三層分層結(jié)構(gòu) 更浪費(fèi)時(shí)間嗎?其實(shí)并不是,因?yàn)樗膶咏Y(jié)構(gòu)相對(duì)于三層結(jié)構(gòu),反而更好的區(qū)分了代碼所屬職責(zé),在熟悉模塊功能職責(zé)后,開發(fā)起來也會(huì)更加順暢。
那這里的 DDD 領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)開發(fā)的成本在哪呢?這個(gè)成本在于對(duì)于一個(gè)復(fù)雜系統(tǒng)又尚未在開發(fā)前期就有非常充足的經(jīng)驗(yàn)來拆分職責(zé)邊界、劃分功能領(lǐng)域、明確編排邏輯和對(duì)未知流程擴(kuò)展的把控上,所帶來的風(fēng)暴模型設(shè)計(jì)成本。
而通常使用的 MVC 結(jié)構(gòu)基本不會(huì)出現(xiàn)這樣的問題,因?yàn)樵趯?shí)際的代碼中,DAO、PO、VO等都是共用的,大家在開發(fā)代碼的時(shí)候,像堆泥球一樣面向過程寫代碼,直接串聯(lián)出產(chǎn)品的PRD功能節(jié)點(diǎn)即可,不用過多的思考解耦和內(nèi)聚。
那不是可以設(shè)計(jì)模式嗎,這就需要看你是站在哪個(gè)維度去思考問題。設(shè)計(jì)模式在這里是戰(zhàn)術(shù)問題的,DDD和MVC是確定戰(zhàn)略問題的,有點(diǎn)像是說:“方向不對(duì),努力白費(fèi)一樣”
那么現(xiàn)在我們?cè)賮砜催@條開發(fā)成本曲線:
架構(gòu)模式,開發(fā)成本曲線
- 與其他兩種分層結(jié)構(gòu)相對(duì)比,使用 DDD 的時(shí)候,需要在前期投入較多的時(shí)間成本來設(shè)計(jì)領(lǐng)域建模,所以前期成本會(huì)更高一些。
- 但隨著業(yè)務(wù)不斷迭代后的邏輯的復(fù)雜性增加,DDD 系統(tǒng)架構(gòu)所開發(fā)的代碼穩(wěn)定性會(huì)更好,也就說明 DDD 更容易擴(kuò)容和維護(hù)。
- 所以框架結(jié)構(gòu)的更換,不是最終增加開發(fā)成本的地方,如果你不做領(lǐng)域建模也不做更多的設(shè)計(jì)思考,那么即使是 DDD 的四層架構(gòu),也能讓你寫出 MVC 的效果。而那些對(duì)業(yè)務(wù)場(chǎng)景經(jīng)驗(yàn)豐富的架構(gòu)師或者研發(fā)人員,已經(jīng)非常明確了各個(gè)業(yè)務(wù)功能的職責(zé)邊界,要實(shí)現(xiàn)一個(gè)系統(tǒng)需求需要完成哪些核心領(lǐng)域服務(wù),在這樣的情況用 DDD 也不會(huì)帶來多少開發(fā)成本,反而更加游刃有余了!這就是為什么說,需要領(lǐng)域?qū)<?,因?yàn)閷<乙呀?jīng)積累了很多的戰(zhàn)略設(shè)計(jì)經(jīng)驗(yàn)
- 此外使用 DDD 領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的模式進(jìn)行開發(fā),除了解決需求的迭代成本,更多的時(shí)候是要面對(duì)公司戰(zhàn)略調(diào)整后,系統(tǒng)的交接、人員的更替和新增,都要在原有的工程架構(gòu)下繼續(xù)迭代開發(fā),否則就要推翻重新做,那樣所面臨的更替成本將更大,同時(shí)又是開發(fā)了一個(gè)與人員綁定不易于交接維護(hù)的工程代碼。
三、架構(gòu)對(duì)比
在了解和掌握 DDD 領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的路上,你一定會(huì)碰到兩個(gè)抽象的釘子 —— “貧血模型”、“充血模型”:
貧血模型:事務(wù)腳本模式,最早起源于 EJB2,到 Spring 進(jìn)入開“春”盛世。
充血模型:領(lǐng)域模型模式,2003年提出,一直到《實(shí)現(xiàn)領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)》的問世,才開啟了 DDD 的大門。但國內(nèi)直到微服務(wù)、低代碼的興起,才開始 DDD 熱
1. MVC
MVC 分層結(jié)構(gòu)將:“狀態(tài)”(數(shù)據(jù),成員對(duì)象)、“行為“(邏輯、過程),分離到不同的對(duì)象中,只有狀態(tài)的對(duì)象(VO -> Value Object) 被稱為貧血模型,只有行為的對(duì)象,就是框架分層中常見的Logic/Service/Manager層(對(duì)應(yīng)到EJB2中的Stateless Session Bean)
MVC 分層結(jié)構(gòu)
- 以應(yīng)用層 Service 使用 DAO、PO 基礎(chǔ)設(shè)施包裝業(yè)務(wù)邏輯的開發(fā)方式,乍一看以為應(yīng)用層是在對(duì)領(lǐng)域建模的實(shí)現(xiàn),”領(lǐng)域?qū)印坝兄S富的對(duì)象鏈接,和真正的領(lǐng)域模型也非常類似,但當(dāng)我們代碼隨著業(yè)務(wù)功能邏輯的逐步實(shí)現(xiàn)中會(huì)慢慢發(fā)現(xiàn),我們寫了一堆的 get/set 對(duì)象,而他們被反復(fù)交叉使用,沒有與任何領(lǐng)域聚合,也就是不具有任何的行為動(dòng)作,只是一堆貧血模型對(duì)象。
- 這種反模式的設(shè)計(jì),其實(shí)完全與面向?qū)ο蟮脑O(shè)計(jì)是背道而馳的,面向?qū)ο蟮脑O(shè)計(jì)更希望行為和數(shù)據(jù)綁定在一起,與之對(duì)比的貧血模型更像是面向過程設(shè)計(jì)。
- 在 MVC 分層結(jié)構(gòu)下,所有的行為都被寫入到 Service 對(duì)象中,最終你會(huì)得到一組事務(wù)處理的過程腳本,從而完美的避開了領(lǐng)域模型設(shè)計(jì)所帶來的好處(清晰的職責(zé)邊界、聚合的功能服務(wù)、清晰的面向?qū)ο?。
2. DDD
DDD 的分層結(jié)構(gòu)也是面向?qū)ο缶幊痰谋举|(zhì):”一個(gè)對(duì)象擁有行為和數(shù)據(jù)“,在領(lǐng)域?qū)影耍簩?duì)象、聚合對(duì)象、倉儲(chǔ)和Service實(shí)現(xiàn)。
DDD 分層結(jié)構(gòu)
- DDD 的分層結(jié)構(gòu)更注重 Domain 領(lǐng)域?qū)拥膶?shí)現(xiàn),由很薄的應(yīng)用層定義接口和編排接口,由領(lǐng)域?qū)幼鼍唧w的實(shí)現(xiàn)。
- 所有的業(yè)務(wù)邏輯都按照各自的職責(zé)邊界拆分成一塊塊的功能領(lǐng)域,每一個(gè)功能領(lǐng)域都是充血模型的結(jié)構(gòu)的具體實(shí)現(xiàn)。
- 那么這樣的代碼最終實(shí)現(xiàn)以后,無論在迭代、維護(hù)、人員更替,都能很好按照領(lǐng)域設(shè)計(jì)文檔找到對(duì)應(yīng)的代碼實(shí)現(xiàn)進(jìn)行開發(fā)。
四、設(shè)計(jì)原則
首先 DDD 的設(shè)計(jì)分為戰(zhàn)略和戰(zhàn)術(shù);
- 戰(zhàn)略設(shè)計(jì):從業(yè)務(wù)視角出發(fā),建立業(yè)務(wù)領(lǐng)域模型、劃分職責(zé)邊界,建立通用語言的界限上下文。頂層戰(zhàn)略設(shè)計(jì)構(gòu)建的領(lǐng)域模型結(jié)構(gòu),是整個(gè)服務(wù)后期編排的重點(diǎn),它確定了功能的職責(zé)邊界、聚合、對(duì)象等,也就決定了后期服務(wù)戰(zhàn)術(shù)實(shí)現(xiàn)的開發(fā)和交付質(zhì)量。重視戰(zhàn)略,才能落地好戰(zhàn)術(shù)!
- 戰(zhàn)術(shù)設(shè)計(jì):從技術(shù)視角出發(fā),側(cè)重于領(lǐng)域模型的技術(shù)實(shí)現(xiàn),完成功能開發(fā)和交付落地。領(lǐng)域設(shè)計(jì)的重點(diǎn)包括:實(shí)體、聚合對(duì)象、值對(duì)象、領(lǐng)域服務(wù)、倉儲(chǔ),還有一個(gè)非常重點(diǎn)的設(shè)計(jì)模式。任何一個(gè)較為復(fù)雜的領(lǐng)域模型實(shí)現(xiàn)都需要考慮設(shè)計(jì)模式的使用,否則即使戰(zhàn)略優(yōu)秀,戰(zhàn)術(shù)也能干回 MVC 去。
在以DDD領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)落地的過程中,要依靠領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的設(shè)計(jì)思想,通過事件風(fēng)暴建立領(lǐng)域模型,合理劃分領(lǐng)域邏輯和物理邊界,建立領(lǐng)域?qū)ο蠹胺?wù)矩陣和服務(wù)架構(gòu)圖,定義符合DDD分層架構(gòu)思想的代碼結(jié)構(gòu)模型,保證業(yè)務(wù)模型與代碼模型的一致性。通過上述設(shè)計(jì)思想、方法和過程,指導(dǎo)團(tuán)隊(duì)按照DDD設(shè)計(jì)思想完成微服務(wù)設(shè)計(jì)和開發(fā)。
拒絕泥球小單體、拒絕污染功能與服務(wù)、拒絕加功能排期一個(gè)月
架構(gòu)出高可用極易符合互聯(lián)網(wǎng)高速迭代的應(yīng)用服務(wù)
物料化、組裝化、可編排的服務(wù),提高人效
要領(lǐng)域驅(qū)動(dòng)設(shè)計(jì),而不是數(shù)據(jù)驅(qū)動(dòng)設(shè)計(jì),也不是界面驅(qū)動(dòng)設(shè)計(jì)
要職能清晰的分層,而不是什么都放的大籮筐
DDD 的領(lǐng)域模型設(shè)計(jì),界限內(nèi)的上下文,可以拆分為獨(dú)立的微服務(wù)。但不僅要從業(yè)務(wù)視角看問題,也要考慮非業(yè)務(wù)的技術(shù)因素,包括:高性能、安全、團(tuán)隊(duì)、技術(shù)異構(gòu)等,這些非業(yè)務(wù)的技術(shù)因素,也會(huì)決定領(lǐng)域模型落地的具體落地。
五、舉個(gè)例子
你說我 MVC 不好,你說我 MVC 貧血模型,PO 類不斷的膨脹,但讓我用 DDD 又都是理論,程序員更喜歡看的是已經(jīng)落地的代碼,告訴我怎么干。
為什么這么難落地呢?因?yàn)閺?MVC 過度到 DDD 描述對(duì)比只是積累了 MVC 失敗的教訓(xùn),但沒有 DDD 成功的經(jīng)驗(yàn),所以更多的時(shí)候想落地 DDD 除了有理論支撐,更需要一份案例擺在面前。
1. 工程結(jié)構(gòu)
所以為了讓更多的碼農(nóng)看到在 DDD 上一條能走的路,專門折騰了個(gè) DDD 分布式抽獎(jiǎng)系統(tǒng),來告訴大家怎么使用 DDD 開發(fā)業(yè)務(wù)需求;
DDD 分布式抽獎(jiǎng)系統(tǒng),工程分布
整體系統(tǒng)架構(gòu)設(shè)計(jì)包含了6個(gè)工程:
- Lottery:分布式部署的抽獎(jiǎng)服務(wù)系統(tǒng),提供抽獎(jiǎng)業(yè)務(wù)領(lǐng)域功能,以分布式部署的方式提供 RPC 服務(wù)。
- Lottery-API:網(wǎng)關(guān)API服務(wù),提供;H5 頁面抽獎(jiǎng)、公眾號(hào)開發(fā)回復(fù)消息抽獎(jiǎng)。
- Lottery-Front:C端用戶系統(tǒng),vue H5 lucky-canvas 大轉(zhuǎn)盤抽獎(jiǎng)界面,講解 vue 工程創(chuàng)建、引入模塊、開發(fā)接口、跨域訪問和功能實(shí)現(xiàn)
- Lottery-ERP:B端運(yùn)營系統(tǒng),滿足運(yùn)營人員對(duì)于活動(dòng)的查詢、配置、修改、審核等操作。
- DB-Router:分庫分表路由組件,開發(fā)一個(gè)基于 HashMap 核心設(shè)計(jì)原理,使用哈希散列+擾動(dòng)函數(shù)的方式,把數(shù)據(jù)散列到多個(gè)庫表中的組件,并驗(yàn)證使用。
- Lottery-Test:測(cè)試驗(yàn)證系統(tǒng),用于測(cè)試驗(yàn)證RPC服務(wù)、系統(tǒng)功能調(diào)用的測(cè)試系統(tǒng)。
2. 流程拆解
當(dāng)我們拿到產(chǎn)品的 RPD 以后,并不是直接上手開發(fā),而是需要從流程中拆解出一份面向?qū)ο笤O(shè)計(jì)的領(lǐng)域服務(wù),舉例;
DDD 分布式抽獎(jiǎng)系統(tǒng),流程拆解
- 拆解功能流程,提煉領(lǐng)域服務(wù),一步步教會(huì)你把一個(gè)業(yè)務(wù)功能流程如何拆解為各個(gè)職責(zé)邊界下的領(lǐng)域模塊,在通過把開發(fā)好的領(lǐng)域服務(wù)在應(yīng)用層進(jìn)行串聯(lián),提供整個(gè)服務(wù)鏈路。
- 通過這樣的設(shè)計(jì)和落地思想,以及在把流程化的功能按照面向?qū)ο蟮乃悸肥褂迷O(shè)計(jì)模式進(jìn)行設(shè)計(jì),讓每一步代碼都變得清晰易懂,這樣實(shí)現(xiàn)出來的代碼也就更加易于維護(hù)和擴(kuò)展了。
- 所以,你在這個(gè)過程中學(xué)會(huì)的不只是代碼開發(fā),還有更多的落地思想實(shí)踐在這里面體現(xiàn)出來。也能為你以后開發(fā)這樣的一個(gè)項(xiàng)目或者在面試過程中,一些實(shí)際復(fù)雜場(chǎng)景問題的設(shè)計(jì)思路,打下不錯(cuò)的基礎(chǔ)。