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