在講DDD之前,我對領域驅動曾經有過一段時間的了解,其實這個概念當我第一次聽的時候發現很泛化,而且很抽象甚至難以理解,后來我發現這個玩意得需要很多時間、很多框架、技術的演進、軟體迭代到了一定的瓶頸,業務愈發復雜而帶來一系列架構轉變和業務重構的捶打,才可以更好的體會和理解這些新詞的意思,本章我就講下我對DDD的理解和認識,也需要讀者能夠熟悉一些常用的設計模式,希望你不是個小白而是一個在業務或者技術有一定的認識,這樣可能看起來不會費勁,也為后面更好的應用于大型互聯網實戰中更上一層,
1、前言
說起DDD的實踐,那就不得不提微服務了,2016年,我剛開始接觸微服務,那時候但是那個時候對技術的熱衷,和身邊的人聊微服務的設計理念,接受度并不高,自己對微服務的理解也沒有現在熟悉,畢竟大家普遍采用的還是集中式架構,
早在今年中旬,為了應對市場需求,我很幸運的參加了公司大型互聯網的架構到當下主流微服務架構轉變的程序,業務的日漸復雜也是可以預見的,微服務的價值確確實實存在,而這個程序中,我和我的技術團隊踩過不少坑,最尖銳的一個問題是:“微服務到底怎么拆分 和設計才算合理,拆多小才叫微服務?”而微服務的邊界歷來也是最容易產生爭議的地方,
隨著阿里巴巴成功完成了中臺戰略轉型,于是,很多大型公司也開啟了中臺數字化戰略轉型,中型公司也根據自身需求躍躍欲試,但也有很多公司由于歷史原因,存在著大量系統重復建設的問題,
作為中臺,需要將通用的可復用的業務能力沉淀到中臺業務模型,實作企業級能力復用,因此中臺面臨的首要問題就是中臺領域模型的重構,而中臺落地時,依然會面臨微服務設計和拆分的問題,這兩個問題一前一后,放在任何一家公司,我想都是一個不小的挑戰,這也是我一直在探索和解決的問題,這兩年,中臺越來越火,微服務越來越熱,參與的人越 來越多,那是否有好的方法來指導中臺和微服務的設計呢?一次偶然的機會我接觸到了 DDD,深入研究后,我發現,運用 DDD 設計思想實作的微服務邊界確實清晰很多,業務領域劃分也十分合理,而我也希望能夠運用在未來我所在的任何一個領域中,古話說,山不向我走來,我便向它走去,哈哈
2、初探DDD
早在2003年就誕生的DDD,06年Eric Evans就著出《領域驅動設計》一書,怎么會“遲到”近20年才大熱的微服務設計呢?DDD全稱是Domain Driven Design,叫領域,驅動,設計;那和我們常說的中臺,微服務之間的關系是什么樣的呢,我想很多人是不明白的甚至會混為一談,
- 中臺的本質是業務模型
- 微服務是業務模型的系統落地
- DDD是一種設計思想,它可以同時指導中臺業務建模和微服務設計
三個概念轉為我們可以理解的一句話:我們要用DDD的設計理念,搭建一個一個的中臺業務模型(業務中臺,技術中臺,資料),最終以微服務相關的技術實作系統落地!!
它們之間就是這樣的一個鐵三角關系,DDD 強調領域模型和微服務設計的一體性,先有領域模型然后才有微服務,而不是脫離領域模型來談微服務設計
通過戰術設計,我們會從領域模型轉向微服務設計和落地,此時,邊界清晰、可持續演進的微服務架構雛形就在你面前了,
所以DDD概念老早提出,Get到他的思想,卻到現在很多公司沒有按照它的方法去實踐,這是為什么呢?主要因為復雜度,
3、令人心塞的遺留系統
DDD是軟體核心復雜性的應對之道,而每家公司都在忙著開發新專案,快速迭代新功能快速上線才是王道!領域驅動對公司,客戶群體來說,太慢了,并且那個時代,業務也沒有這么復雜,DDD遠遠發揮不出來應有的優勢,但是最近幾年,事情卻慢慢發生了變化,很多公司的產品迭代至今,程式以及凌亂不堪,維護成本越來越高,需要重構優化改造,
許多動輒數千行甚至上萬行的大函式和大物件,是軟體退化的重災區,為什么會這樣?深刻思考后很快意識到了根源:這是軟體業務由簡單像復雜轉變的必然結果,軟體會越來越復雜,代碼會越來越多,這樣就不能在原有的程式結構里塞代碼,而是要調整程式結構,該解耦的解耦,該拆分的拆分,再實作新功能才能保證設計質量,
那么怎樣調整呢,也許第一次第二次我們想的清楚,但是第十次三十次我們就想不清楚了,設計開始迷失方向怎么辦?再次陷入了沉思;這個時候,DDD就可以很好的解決這些問題,業務越來越復雜而難以理解,這不是個例而是當今所有軟體都必須面對的難題,運用DDD當系統業務變得越來越復雜時,將我們對業務的理解繪制成【領域模型】可以正確的指導軟體開發,
當系統變更時,將變更業務通過領域模型,還原到真實世界,再根據真實世界去變更領域模型,根據領域模型的變更指導程式變更,從而低成本的維護一個系統達到快速迭代,快速交付的合格軟體,這是DDD的設計核心,這么說是比較抽象的,我舉個例子,如果我要設計一個支付交易系統,從應用代碼層面,如果我要扣款的時候新增加很多功能如vip,限時折扣,某個/類商品折扣,很快就迎來了第一次的需求變更,我們最大概率會這么做,在原有的付款方法中加入if陳述句,然后判斷邏輯;如果對于領域驅動的思想,還原成真實世界,再去修改領域模型,分析付款和折扣之間在現實世界的關系,你可能會認為折扣是在付款程序中進行的,所以你會認為折扣應該寫在付款中,這樣思考對嗎,回顧下我們軟體設計的【單一職責】原則,軟體系統中每個元素只完成自己職責范圍的事情,而將其他的事情交給別人去做,我只是呼叫,重要是我們如何理解這個【職責】,以往的理解是我要做某件事,那么與此相關的所有事情都是它的職責,因為這個錯誤理解帶來了很多錯誤設計,從而將折扣寫到付款功能中,
一個職責是一個軟體變化的原因,當用戶提出一個需求變更時,為了實作這個變更,而修改的軟體成本越低,軟體設計質量就越高,如果為實作這個需求,需要修改三個模塊,完后這三個模塊都要集體測驗,其維護成本非常高,而怎么樣才能把修改降到最低呢,最現實的方法就是只修改一個模塊,怎么樣才能在每次變更的時候就修改一個模塊實作新需求呢?這就需要平時不斷的整理代碼,將因同一個原因而變更的代碼都放在一起,將因不同原因而變更的代碼放在不同的模塊、類中,
回到剛剛的案例中,只需要回答兩個問題:
- 當付款發生變更時,折扣是不是一定要變?(否)
- 當折扣發生變更時,付款是不是一定要變?(否)
當這兩個問題的答案都是否定的時候,那付款和折扣就是這個軟體變化的不同原因,那放在同一個方法,同一個類中合適嗎?很明顯不合適,就應該將折扣從付款中提取出來,單獨放在一個類中,所以根據折扣我們可以單獨拆出來形成一個獨立的真實世界,來繪制領域模型,如下:
沒有領域驅動之前添加折扣,很多人會把折扣寫在付款邏輯中,
添加折扣后我重新折扣進行了分析,對領域模型進行了修改,以折扣介面為基礎,以此設計了很多不同型別的折扣方式,這樣的設計當付款功能發生變更時,不會影響折扣,而折扣發生變更時也不會影響付款,而折扣之間也不會存在相互影響,其實這就是策略模式的體現!!
緊接著會在這個版本的基礎上進行程式設計,設計的時候加入設計模式的內容,將折扣功能做成了折扣策略介面,與各種折扣策略的實作類,當哪個折扣型別發生變更時,就修改哪個折扣策略的實作類,當要增加新的型別就再寫一個新的策略實作類,從而設計質量得到提高,
于是第二個業務場景過來了,我要增加vip會員功能,對不同型別的vip會員進行不同折扣,付款的時候為vip發福利,享受特權等,
拿到這個需求我們又應該如何設計呢?同樣先回到領域模型,分析用戶和vip會員的關系,付款與vip會員的關系,回答兩個問題:
- 用戶發生變更時,vip會員是否一定要變(否)
- vip會員發生變更時,用戶是否一定要變(否)
結合真實世界,很明顯這兩個答案又是否,用戶和vip是兩個不同的事物,用戶要做的是用戶注冊,變更注銷等,vip要做的是折扣,福利,特權,而付款與vip會員的關系呢?
- 付款發生變更時,vip會員是否一定要變(否)
- vip會員發生變更時,付款是否一定要變(否)
付款和vip之間也是兩個不同的事物,之間也是呼叫關系,在付款中呼叫會員折扣,福利和特權,那么vip自然也就是獨立出來的一個領域模型
同理如果增加支付方式,我們也要分析扣款和支付方式之間的關系
- 付款發生變更時,支付方式是否一定要變(否)
- 支付方式發生變更時,付款是否一定要變(否)
自然也是獨立出來的兩個物體和現實世界的場景,我們一樣可以設計
因為是要和外部系統進行對接,為了避免外部系統對我們的影響最小化,我們可以加入配接器模式,訂單Service在進行支付的時候,不再是一個一個外部的支付介面,而是支付方式介面,與外部系統解耦保證支付方式介面是穩定的,那么訂單Service介面就是穩定的,比如支付寶支付介面發生變更時,影響的只限于支付寶的Adapter,當要增加一個新的支付方式就增加一個Adapter就可以,日后修改范圍就縮小了,
講到這里,是不是對遺留系統的問題來深刻領會到【領域驅動】的核心思想?
4、轉型利器微服務
上面曾說過,微服務是業務模型的系統落地,微服務本身不是一項技術,而是對業務模型的系統實際解決方案,可是微服務不是銀彈,也有很多坑,當按照模塊拆分的時候,每次變更都需要修改很多的微服務,不但多個團隊都要變更,還要同時打包同時升級,沒有降低維護成本,反而使得發布比過去更麻煩,那不如不用微服務?那是微服務不好嗎,我又一度陷入了沉思,這個時候我去翻閱一些資料的時候,Martin Flower定義微服務時提到的“小而專”,我們Get到了“小”,缺忽略了“專”,這里的專就是要小團隊專業維護,就是盡量讓每次的需求變更,交給小團隊獨立完成,讓需求變更落到某個微服務變更,這樣才可以發揮微服務的優勢,
經過深入想后才發現,微服務的設計真的不僅僅是一個技術架構更進的事,而是對原有設計提出了更高的要求,即服務內高內聚,服務間低耦合,而我們沒有過硬的能力和設計經驗,就不能把微服務帶來的復雜度歸咎于微服務的弊相關,只能說我們沒有更好的理解到微服務核心給我們解決了什么問題,那么誰來拯救微服務現在的情況?
同樣還是DDD,我們轉型微服務的重要根源,就是系統的復雜性,即系統規模越來愈大,維護越來越困難,才需要拆分微服務,然而拆分每個微服務不代表都各自運行,而是彼此協同組織在一起,而DDD恰恰就是幫助我們組織微服務的實作方法,
要讓DDD在團隊中用的好,需要一個支持DDD與微服務的技術中臺!有了技術中臺,開發團隊就有更多的精力放在對用戶業務的理解,對業務痛點的理解快速開發交付,這樣不僅撰寫代碼門檻減少了技術降低了,還使得日后變更更加容易,
那么DDD具體如何解決微服務拆分難題呢?在下一章節我會深入講述【領域模型】的眾多名詞,概念比如限界背景關系,聚合,倉庫,工廠等,這些名詞告訴我們【領域模型】會將一個系統劃分成多個子域,每個子域都是一個一個獨立業務場景;同時每個子域實作的就是“限界背景關系”等等類似這樣的抽象世界,
5、技術中臺又是什么
在以往建設的系統中,基本都分為前臺和后臺,前臺一般指用戶互動的UI界面,后臺是指服務端完成業務邏輯的操作,然而在開發很多業務系統中,有一些內容是共用的部分,甚至在未來的開發系統中也會使用到,所以如果我們能把這些內容提取出來做成公用組件,那未來開發系統就簡單了,不用每次重復開發,直接復用這些組件就可以,那么這些復用的組件到除錯于前臺還是后臺呢?其實都不屬于,既包含前臺的界面,也包含后臺的邏輯,這就是中臺的概念!
中臺:將以往業務系統中可以復用的前臺與后臺代碼剝離個性,提取共性,形成公用組件,
所以阿里提出了小前臺,大中臺的戰略得到了業界普遍的認可,從分類上看,中臺分為業務中臺,技術中臺,資料中臺;
那么清楚這些概念后,自然就會知道DDD與微服務底層技術進行封裝,從而支持開發團隊在未來實作快速交付,以應對激烈競爭的市場,所以首先要清楚實作快速交付的技術特點,才能清楚這個技術中臺如何建設,
我們大多數公司的團隊是這樣的,從需求交付的整個程序,要經歷多個部門的互動,才能完成最終交付,大量的時間被耗費在了部門之間的溝通協調中, 這樣的團隊叫做煙囪式開發團隊,
這樣的開發團隊會導致煙囪式的軟體開發,每個功能都要設計大量的controller,service,dao需要撰寫大量的代碼,很多是重復的,量越大bug越多,變更困難,
統一的發布制約了交付速度,對于業務負責人把需求交給多個團隊開發時,A團隊發布的可能需要1周就可以完成,但是A團隊完成后無法立即交付,而B團隊要開發兩周,必須要等B開發完成后再一起交付給客戶,即便開發速度再快,不能立即交付,就無法帶來價值
如何解決這個問題?將桶裝的煙囪橫著來,將所有的需求,ui,資料庫,運維等作業都交給這些模塊單獨的特性團隊,所有與購物相關的功能都是這個團隊來維護,每個這樣的團隊負責整個模塊的交付程序,不再需要等待其他團隊每個功能都要一起發布,這就是特性團隊,而這樣的團隊成本是非常高的,幾乎每個成員都是全堆疊工程師,
通過對技術的選型,構建技術中臺比如UI,應用,資料庫進行了封裝,然后以API介面的方式開放給上層業務,這就是大前端,是一種職能的轉變,不是我們業界說的“前端”,而是業務人員更加關注業務,深刻理解業務并快速應對市場需求的變化,架構團隊從業務開發角度進行提煉共性,保留個性,將這些共性沉淀到技術中臺中,
如何打造一個強大的技術中臺呢?在Martin Flower《企業應用架構模式》中指出一個概念,叫命令與查詢職責分離模式,命令就是增刪改操作,其中指出所有命令的增刪改操作,應當采用【領域驅動】設計思想進行軟體設計,所有查詢的功能應當采用事務腳本模式,即直接通過SQL陳述句進行查詢,
技術中臺的實作只存在統一的一個Controller和單個Dao,和以往不同的是,每個功能有各自的前端UI,呼叫后臺的邏輯時不再呼叫各自的Controller,而是統一呼叫一個Controller,每個功能的前端呼叫這個Controller傳遞的引數是不一樣的,將Service和Dao裝配起來后,形成一個一個的bean,前端只知道呼叫的是bean,并不需要知道呼叫的是哪個Service,這樣的設計既保證了安全性,實作前后端分離,也實作解耦,緊接著前端還要傳遞一個Method,即呼叫的是哪個方法和哪個json物件,這樣Controller就可以通過反射進行相應的操作,通過規范和契約,我們認為前端開發人員已經知道呼叫后端的哪個bean,哪個method,以及什么格式的json,
舉個例子:前端統一只訪問ORMController,訪問localhost:9003/orm/{bean}/method,bean是Spring的bean的id,method是bean中需要呼叫的方法(此處不支持方法的重寫,如果是重寫,則會呼叫同名方法中的最后一個),如果呼叫的方法有值物件,必須將值物件放在方法的第一個引數上;如果呼叫的方法既有值物件,又有其他引數,則值物件中的屬性與其他引數都放在該json物件中,如果要進行權限校驗,需要在ORMController之前做,建議在網關或者filter校驗,
具體的流程是
那么單Dao怎么設計,比如下面的圖,將每個值物件對應的表通過xml的方式進行對應,并最終完成資料庫的持久化操作,值物件中有很多屬性變數,最終只有需要持久化的屬性變數才需要配置,
有了以上的設計,每個Service在Spring中都是統一注入BasicDao,如果要使用DDD的功能支持,注入通用倉庫Repository,如果要使用Redis快取,注入Redis相關的Repository就可以,這里要注意,基于這類技術中臺框架的業務開發,包括Spring配置,Mybatis配置,vObj的配置建議都采用XML檔案的形式,不要采用注解,
單Dao設計流程:
- 單Dao呼叫vObjFactory.getVObj(class)獲取配置資訊vObj
- 根據vObj.getTable()獲得對應的表名
- for回圈拿到vObj.getProperties().getColumn()獲得值物件對應的欄位,反射拿到具體欄位對應到的值,拼接成sql
- 進行資料庫操作
接下來看下查詢功能的技術中臺設計
與增刪改不同的是,Service只有一個,往Service中注入的是不同的Dao,可以裝配成不同的bean,查詢部分不需要傳遞method引數,這樣前端查詢的時候傳遞不同的json引數就可以實作不同的查詢,
比如進行查詢的時候,前端輸入localhost:9003/query/{bean},如下圖
前端個Service查詢互動如下圖
所有的查詢都是一個Service,注入了不同的Dao,配置成不同的Bean完成不同的查詢,每個Dao可以通過Mybatis框架注入,配置不同的mapper完成不同的查詢,然后將其注入Spring中完成配置,
對于Service和Dao的互動如下
但是在某些業務中需要個性的在查詢前進行某些處理,現在設計中只有一個Service,怎么辦呢?
我們提個概念,叫鉤子Hook,在QueryService中設計成空方法,因此呼叫他們跟沒有呼叫是一樣的,如果需要在查詢前后添加某些處理,則撰寫一個QueryService的子類并重寫這兩個方法,只要在Spring配置中配置這個子類即可,
通過以上中臺設計,會簡化很多,設計一個普通的查詢,只需要制作一個Mybatis的查詢陳述句配置,在Spring中配置一個bean,就可以通過前端進行查詢,甚至不需要新增class,除非在查詢前后要做些什么,
本章可能最后一個章節比較抽象,后續我會拿實戰案例Demo來演示技術中臺是如何落地,初探了DDD,理論一定要具備,才有更好的實踐,
下一章節,走進DDD如何指導架構和業務設計的,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/379503.html
標籤:其他






















