萬字長文,結(jié)合電商支付業(yè)務(wù)一文搞懂DDD
作者范鋼,曾任航天信息首席架構(gòu)師,《大話重構(gòu)》一書的作者。本文結(jié)合電商支付場景詳細(xì)描述了領(lǐng)域驅(qū)動模型的實際應(yīng)用。
2004 年,軟件大師 Eric Evans 的不朽著作《領(lǐng)域驅(qū)動設(shè)計:軟件核心復(fù)雜性應(yīng)對之道》面世,從書名可以看出,這是一本應(yīng)對軟件系統(tǒng)越來越復(fù)雜的方法論的圖書。然而,在當(dāng)時,中國的軟件業(yè)才剛剛起步,軟件系統(tǒng)還沒有那么復(fù)雜,即使維護了幾年,軟件退化了,不好維護了,推倒重新開發(fā)就好了。因此,在過去的那么多年里,真正運用領(lǐng)域驅(qū)動設(shè)計開發(fā)(DDD)的團隊并不多。一套優(yōu)秀的方法論,因為現(xiàn)實階段的原因而一直不溫不火。
不過,這些年隨著中國軟件業(yè)的快速發(fā)展,軟件規(guī)模越來越大,生命周期也越來越長,推倒重新開發(fā)的成本和風(fēng)險越來越大。這時,軟件團隊急切需要在較低成本的狀態(tài)下持續(xù)維護一個系統(tǒng)很多年。然而,事與愿違。隨著時間的推移,程序越來越亂,維護成本越來越高,軟件退化成了無數(shù)軟件團隊的噩夢。
這時,微服務(wù)架構(gòu)成了規(guī)?;浖慕鉀Q之道。不過,微服務(wù)對設(shè)計提出了很高的要求,強調(diào)“小而專、高內(nèi)聚”,否則就不能發(fā)揮出微服務(wù)的優(yōu)勢,甚至可能令問題更糟糕。
因此,微服務(wù)的設(shè)計,微服務(wù)的拆分都需要領(lǐng)域驅(qū)動設(shè)計的指導(dǎo)。那么,領(lǐng)域驅(qū)動為什么能解決軟件規(guī)模化的問題呢?我們先從問題的根源談起,即軟件退化。
軟件退化的根源
最近 10 年的互聯(lián)網(wǎng)發(fā)展,從電子商務(wù)到移動互聯(lián),再到“互聯(lián)網(wǎng)+”與傳統(tǒng)行業(yè)的互聯(lián)網(wǎng)轉(zhuǎn)型,是一個非常痛苦的轉(zhuǎn)型過程。而近幾年的人工智能與 5G 技術(shù)的發(fā)展,又會帶動整個產(chǎn)業(yè)向著大數(shù)據(jù)與物聯(lián)網(wǎng)發(fā)展,另一輪的技術(shù)轉(zhuǎn)型已經(jīng)拉開帷幕。
那么,在這個過程中,一方面會給我們帶來諸多的挑戰(zhàn),另一方面又會給我們帶來無盡的機會,它會帶來更多的新興市場、新興產(chǎn)業(yè)與全新業(yè)務(wù),給我們帶來全新的發(fā)展機遇。
然而,在面對全新業(yè)務(wù)、全新增長點的時候,我們能不能把握住這樣的機遇呢?我們期望能把握住,但每次回到現(xiàn)實,回到正在維護的系統(tǒng)時,卻令人沮喪。我們的軟件總是經(jīng)歷著這樣的輪回,軟件設(shè)計質(zhì)量最高的時候是第一次設(shè)計的那個版本,當(dāng)?shù)谝粋€版本設(shè)計上線以后就開始各種需求變更,這常常又會打亂原有的設(shè)計。
因此,需求變更一次,版本迭代一次,軟件就修改一次,軟件修改一次,質(zhì)量就下降一次。不論第一次的設(shè)計質(zhì)量有多高,軟件經(jīng)歷不了幾次變更,就進入一種低質(zhì)量、難以維護的狀態(tài)。進而,團隊就不得不在這樣的狀態(tài)下,以高成本的方式不斷地維護下去,維護很多年。
這時候,維護好原有的業(yè)務(wù)都非常不易,又如何再去期望未來更多的全新業(yè)務(wù)呢?比如,這是一段電商網(wǎng)站支付功能的設(shè)計,最初的版本設(shè)計質(zhì)量還是不錯的:
當(dāng)?shù)谝粋€版本上線以后,很快就迎來了第一次變更,變更的需求是增加商品折扣功能,并且這個折扣功能還要分為限時折扣、限量折扣、某類商品的折扣、某個商品的折扣。當(dāng)我們拿到這個需求時怎么做呢?很簡單,增加一個 if 語句,if 限時折扣就怎么怎么樣,if 限量折扣就怎么怎么樣……代碼開始膨脹了。
接著,第二次變更需要增加 VIP 會員,除了增加各種金卡、銀卡的折扣,還要為會員發(fā)放各種福利,讓會員享受各種特權(quán)。為了實現(xiàn)這些需求,我們又要在 payoff() 方法中加入更多的代碼。
第三次變更增加的是支付方式,除了支付寶支付,還要增加微信支付、各種銀行卡支付、各種支付平臺支付,此時又要塞入一大堆代碼。經(jīng)過這三次變更,你可以想象現(xiàn)在的 payoff() 方法是什么樣子了吧,變更是不是就可以結(jié)束了呢?其實不能,接著還要增加更多的秒殺、預(yù)訂、閃購、眾籌,以及各種返券。程序變得越來越亂而難以閱讀和維護,每次變更也變得越來越困難。
問題來了:為什么軟件會退化,會隨著變更而設(shè)計質(zhì)量下降呢?在這個問題上,我們必須尋找到問題的根源,才能對癥下藥、解決問題。
要探尋軟件退化的根源,先要從探尋軟件的本質(zhì)及其規(guī)律開始,軟件的本質(zhì)就是對真實世界的模擬,每個軟件都能在真實世界中找到它的影子。因此,軟件中業(yè)務(wù)邏輯正確與否的唯一標(biāo)準(zhǔn)就是是否與真實世界一致。如果一致,則軟件是 OK 的;不一致,則用戶會提 Bug、提新需求。
在這里發(fā)現(xiàn)了一個非常重要的線索,那就是,軟件要做成什么樣,既不由我們來決定,也不由用戶來決定,而是由客觀世界決定。用戶為什么總在改需求,是因為他們也不確定客觀世界的規(guī)則,只有遇到問題了他們才能想得起來。因此,對于我們來說,與其唯唯諾諾地按照用戶的要求去做軟件,不如在充分理解業(yè)務(wù)的基礎(chǔ)上去分析軟件,這樣會更有利于我們減少軟件維護的成本。
那么,真實世界是怎樣的,我們就怎樣開發(fā)軟件,不就簡單了嗎?其實并非如此,因為真實世界是非常復(fù)雜的,要深刻理解真實世界中的這些業(yè)務(wù)邏輯是需要一個過程的。因此,我們最初只能認(rèn)識真實世界中那些簡單、清晰、易于理解的業(yè)務(wù)邏輯,把它們做到我們的軟件里,即每個軟件的第一個版本的需求總是那么清晰明了、易于設(shè)計。
然而,當(dāng)我們把第一個版本的軟件交付用戶使用的時候,用戶卻會發(fā)現(xiàn),還有很多不簡單、不明了、不易于理解的業(yè)務(wù)邏輯沒做到軟件里。這在使用軟件的過程中很不方便,和真實業(yè)務(wù)不一致,因此用戶就會提 Bug、提新需求。
在我們不斷地修復(fù) Bug,實現(xiàn)新需求的過程中,軟件的業(yè)務(wù)邏輯也會越來越接近真實世界,使得我們的軟件越來越專業(yè),讓用戶感覺越來越好用。但是,在軟件越來越接近真實世界的過程中,業(yè)務(wù)邏輯就會變得越來越復(fù)雜,軟件規(guī)模也越來越龐大。
你一定有這樣一個認(rèn)識:簡單軟件有簡單軟件的設(shè)計,復(fù)雜軟件有復(fù)雜軟件的設(shè)計。
比如,現(xiàn)在的需求就是將用戶訂單按照“單價 × 數(shù)量”公式來計算應(yīng)付金額,那么在一個 PaymentBus 類中增加一個 payoff() 方法即可,這樣的設(shè)計沒有問題。不過,如果現(xiàn)在的需要在付款的過程中計算各種折扣、各種優(yōu)惠、各種返券,那么我們必然會做成一個復(fù)雜的程序結(jié)構(gòu)。
但是,真實情況卻不是這樣的。真實情況是,起初我們拿到的需求是那個簡單需求,然后在簡單需求的基礎(chǔ)上進行了設(shè)計開發(fā)。但隨著軟件的不斷變更,軟件業(yè)務(wù)邏輯變得越來越復(fù)雜,軟件規(guī)模不斷擴大,逐漸由一個簡單軟件轉(zhuǎn)變成一個復(fù)雜軟件。
這時,如果要保持軟件設(shè)計質(zhì)量不退化,就應(yīng)當(dāng)逐步調(diào)整軟件的程序結(jié)構(gòu),逐漸由簡單的程序結(jié)構(gòu)轉(zhuǎn)變?yōu)閺?fù)雜的程序結(jié)構(gòu)。如果我們總是這樣做,就能始終保持軟件的設(shè)計質(zhì)量,不過非常遺憾的是,我們以往在維護軟件的過程中卻不是這樣做的,而是不斷地在原有簡單軟件的程序結(jié)構(gòu)下,往 payoff() 方法中塞代碼,這樣做必然會造成軟件的退化。
也就是說,軟件退化的根源不是版本迭代和需求變更,版本迭代和需求變更只是一個誘因。如果每次軟件變更時,適時地進行解耦,進行功能擴展,再實現(xiàn)新的功能,就能保持高質(zhì)量的軟件設(shè)計。但如果在每次軟件變更時沒有調(diào)整程序結(jié)構(gòu),而是在原有的程序結(jié)構(gòu)上不斷地塞代碼,軟件就會退化。這就是軟件發(fā)展的規(guī)律,軟件退化的根源。
杜絕軟件退化:兩頂帽子
前面談到,要保持軟件設(shè)計質(zhì)量不退化,必須在每次需求變更的時候,對原有的程序結(jié)構(gòu)適當(dāng)?shù)剡M行調(diào)整。那么應(yīng)當(dāng)怎樣進行調(diào)整呢?還是回到前面電商網(wǎng)站付款功能的那個案例,看看每次需求變更應(yīng)當(dāng)怎樣設(shè)計。
在交付第一個版本的基礎(chǔ)上,很快第一次需求變更就到來了。第一次需求變更的內(nèi)容如下。
增加商品折扣功能,該功能分為以下幾種類型:
-
限時折扣
-
限量折扣
-
對某類商品進行折扣
-
對某個商品進行折扣
-
不折扣
以往我們拿到這個需求,就很不冷靜地開始改代碼,修改成了如下一段代碼:
這里增加了的 if else 語句,并不是一種好的變更方式。如果每次都這樣變更,那么軟件必然就會退化,進入難以維護的狀態(tài)。這種變更為什么不好呢?因為它違反了“開放-封閉原則”。
開閉原則(OCP) 分為開放原則與封閉原則兩部分。
-
開放原則:我們開發(fā)的軟件系統(tǒng),對于功能擴展是開放的(Open for Extension),即當(dāng)系統(tǒng)需求發(fā)生變更時,可以對軟件功能進行擴展,使其滿足用戶新的需求。
-
封閉原則:對軟件代碼的修改應(yīng)當(dāng)是封閉的(Close for Modification),即在修改軟件的同時,不要影響到系統(tǒng)原有的功能,所以應(yīng)當(dāng)在不修改原有代碼的基礎(chǔ)上實現(xiàn)新的功能。也就是說,在增加新功能的時候,新代碼與老代碼應(yīng)當(dāng)隔離,不能在同一個類、同一個方法中。
前面的設(shè)計,在實現(xiàn)新功能的同時,新代碼與老代碼在同一個類、同一個方法中了,違反了“開閉原則”。怎樣才能既滿足“開閉原則”,又能夠?qū)崿F(xiàn)新功能呢?在原有的代碼上你發(fā)現(xiàn)什么都做不了!難道“開閉原則”錯了嗎?
問題的關(guān)鍵就在于,當(dāng)我們在實現(xiàn)新需求時,應(yīng)當(dāng)采用“兩頂帽子”的方式進行設(shè)計,這種方式就要求在每次變更時,將變更分為兩個步驟。
兩頂帽子:
-
在不添加新功能的前提下,重構(gòu)代碼,調(diào)整原有程序結(jié)構(gòu),以適應(yīng)新功能;
-
實現(xiàn)新的功能。
按以上案例為例,為了實現(xiàn)新的功能,我們在原有代碼的基礎(chǔ)上,在不添加新功能的前提下調(diào)整原有程序結(jié)構(gòu),我們抽取出了 Strategy 這樣一個接口和“不折扣”這個實現(xiàn)類。這時,原有程序變了嗎?沒有。但是程序結(jié)構(gòu)卻變了,增加了這樣一個接口,稱之為“可擴展點”。在這個可擴展點的基礎(chǔ)上再實現(xiàn)各種折扣,既能滿足“開放-封閉原則”來保證程序質(zhì)量,又能夠滿足新的需求。當(dāng)日后發(fā)生新的變更時,什么類型的折扣有變化就修改哪個實現(xiàn)類,添加新的折扣類型就增加新的實現(xiàn)類,維護成本得到降低。
“兩頂帽子”的設(shè)計方式意義重大。過去,我們每次在設(shè)計軟件時總是擔(dān)心日后的變更,就很不冷靜地設(shè)計了很多所謂的“靈活設(shè)計”。然而,每一種“靈活設(shè)計”只能應(yīng)對一種需求變更,而我們又不是先知,不知道日后會發(fā)生什么樣的變更。最后的結(jié)果就是,我們期望的變更并沒有發(fā)生,所做的設(shè)計都變成了擺設(shè),它既不起什么作用,還增加了程序復(fù)雜度;我們沒有期望的變更發(fā)生了,原有的程序依然不能解決新的需求,程序又被打回了原形。因此,這樣的設(shè)計不能真正解決未來變更的問題,被稱為“過度設(shè)計”。
有了“兩頂帽子”,我們不再需要焦慮,不再需要過度設(shè)計,正確的思路應(yīng)當(dāng)是“活在今天的格子里做今天的事兒”,也就是為當(dāng)前的需求進行設(shè)計,使其剛剛滿足當(dāng)前的需求。所謂的“高質(zhì)量的軟件設(shè)計”就是要掌握一個平衡,一方面要滿足當(dāng)前的需求,另一方面要讓設(shè)計剛剛滿足需求,從而使設(shè)計最簡化、代碼最少。這樣做,不僅軟件設(shè)計質(zhì)量提高了,設(shè)計難點也得到了大幅度降低。
簡而言之,保持軟件設(shè)計不退化的關(guān)鍵在于每次需求變更的設(shè)計,只有保證每次需求變更時做出正確的設(shè)計,才能保證軟件以一種良性循環(huán)的方式不斷維護下去。這種正確的設(shè)計方式就是“兩頂帽子”。
但是,在實踐“兩頂帽子”的過程中,比較困難的是第一步。在不添加新功能的前提下,如何重構(gòu)代碼,如何調(diào)整原有程序結(jié)構(gòu),以適應(yīng)新功能,這是有難度的。很多時候,第一次變更、第二次變更、第三次變更,這些事情還能想清楚;但經(jīng)歷了第十次變更、第二十次變更、第三十次變更,這些事情就想不清楚了,設(shè)計開始迷失方向。
那么,有沒有一種方法,讓我們在第十次變更、第二十次變更、第三十次變更時,依然能夠找到正確的設(shè)計呢?有,那就是“領(lǐng)域驅(qū)動設(shè)計”。
保持軟件質(zhì)量:領(lǐng)域驅(qū)動
前面談到,軟件的本質(zhì)就是對真實世界的模擬。因此,我們會有一種想法,能不能將軟件設(shè)計與真實世界對應(yīng)起來,真實世界是什么樣子,那么軟件世界就怎么設(shè)計。如果是這樣的話,那么在每次需求變更時,將變更還原到真實世界中,看看真實世界是什么樣子的,根據(jù)真實世界進行變更。這樣,日后不論怎么變更,經(jīng)過多少輪變更,都按照這樣的方法進行設(shè)計,就不會迷失方向,設(shè)計質(zhì)量就可以得到保證,這就是“領(lǐng)域驅(qū)動設(shè)計”的思想。
那么,如何將真實世界與軟件世界對應(yīng)起來呢?這樣的對應(yīng)就包括以下三個方面的內(nèi)容:
-
真實世界有什么事物,軟件世界就有什么對象;
-
真實世界中這些事物都有哪些行為,軟件世界中這些對象就有哪些方法;
-
真實世界中這些事物間都有哪些關(guān)系,軟件世界中這些對象間就有什么關(guān)聯(lián)。
真實世界與軟件世界的對應(yīng)圖
在領(lǐng)域驅(qū)動設(shè)計中,就將以上三個對應(yīng),先做成一個領(lǐng)域模型,然后通過這個領(lǐng)域模型指導(dǎo)程序設(shè)計;在每次需求變更時,先將需求還原到領(lǐng)域模型中分析,根據(jù)領(lǐng)域模型背后的真實世界進行變更,然后根據(jù)領(lǐng)域模型的變更指導(dǎo)軟件的變更,設(shè)計質(zhì)量就可以得到提高。
結(jié)合電商支付實際演練DDD
現(xiàn)在,我們以電商網(wǎng)站的支付功能為例,來演練一下基于 DDD 的軟件設(shè)計及其變更的過程。
運用 DDD 進行軟件設(shè)計
開發(fā)人員在最開始收到的關(guān)于用戶付款功能的需求描述是這樣的:
-
在用戶下單以后,經(jīng)過下單流程進入付款功能;
-
通過用戶檔案獲得用戶名稱、地址等信息;
-
記錄商品及其數(shù)量,并匯總付款金額;
-
保存訂單;
-
通過遠(yuǎn)程調(diào)用支付接口進行支付。
以往當(dāng)拿到這個需求時,開發(fā)人員往往草草設(shè)計以后就開始編碼,設(shè)計質(zhì)量也就不高。
而采用領(lǐng)域驅(qū)動的方式,在拿到新需求以后,應(yīng)當(dāng)先進行需求分析,設(shè)計領(lǐng)域模型。按照以上業(yè)務(wù)場景,可以分析出:
-
該場景中有“訂單”,每個訂單都對應(yīng)一個用戶;
-
一個用戶可以有多個用戶地址,但每個訂單只能有一個用戶地址;
-
此外,一個訂單對應(yīng)多個訂單明細(xì),每個訂單明細(xì)對應(yīng)一個商品,每個商品對應(yīng)一個供應(yīng)商。
最后,我們對訂單可以進行“下單”“付款”“查看訂單狀態(tài)”等操作。因此形成了以下領(lǐng)域模型圖:
有了這樣的領(lǐng)域模型,就可以通過該模型進行以下程序設(shè)計:
通過領(lǐng)域模型的指導(dǎo),將“訂單”分為訂單 Service 與值對象,將“用戶”分為用戶 Service 與值對象,將“商品”分為商品 Service 與值對象……然后,在此基礎(chǔ)上實現(xiàn)各自的方法。
商品折扣的需求變更
當(dāng)電商網(wǎng)站的付款功能按照領(lǐng)域模型完成了第一個版本的設(shè)計后,很快就迎來了第一次需求變更,即增加折扣功能,并且該折扣功能分為限時折扣、限量折扣、某類商品的折扣、某個商品的折扣與不折扣。當(dāng)我們拿到這個需求時應(yīng)當(dāng)怎樣設(shè)計呢?很顯然,在 payoff() 方法中去插入 if else 語句是不 OK 的。這時,按照領(lǐng)域驅(qū)動設(shè)計的思想,應(yīng)當(dāng)將需求變更還原到領(lǐng)域模型中進行分析,進而根據(jù)領(lǐng)域模型背后的真實世界進行變更。
這是上一個版本的領(lǐng)域模型,現(xiàn)在我們要在這個模型的基礎(chǔ)上增加折扣功能,并且還要分為限時折扣、限量折扣、某類商品的折扣等不同類型。這時,我們應(yīng)當(dāng)怎么分析設(shè)計呢?
首先要分析付款與折扣的關(guān)系。
付款與折扣是什么關(guān)系呢?你可能會認(rèn)為折扣是在付款的過程中進行的折扣,因此就應(yīng)當(dāng)將折扣寫到付款中。這樣思考對嗎?我們應(yīng)當(dāng)基于什么樣的思想與原則來設(shè)計呢?這時,另外一個重量級的設(shè)計原則應(yīng)該出場了,那就是“單一職責(zé)原則”。
單一職責(zé)原則:軟件系統(tǒng)中的每個元素只完成自己職責(zé)范圍內(nèi)的事,而將其他的事交給別人去做,我只是去調(diào)用。
單一職責(zé)原則是軟件設(shè)計中一個非常重要的原則,但如何正確地理解它成為一個非常關(guān)鍵的問題。在這句話中,準(zhǔn)確理解的關(guān)鍵就在于“職責(zé)”二字,即自己職責(zé)的范圍到底在哪里。以往,我們錯誤地理解這個“職責(zé)”就是做某一個事,與這個事情相關(guān)的所有事情都是它的職責(zé),正因為這個錯誤的理解,帶來了許多錯誤的設(shè)計,而將折扣寫到付款功能中。那么,怎樣才是對“職責(zé)”正確的理解呢?
“一個職責(zé)就是軟件變化的一個原因”是著名的軟件大師 Bob 大叔在他的《敏捷軟件開發(fā):原則、模式與實踐》中的表述。但這個表述過于精簡,很難深刻地理解其中的內(nèi)涵。這里我好好解讀一下這句話。
先思考一下什么是高質(zhì)量的代碼?你可能立即會想到“低耦合、高內(nèi)聚”,以及各種設(shè)計原則,但這些評價標(biāo)準(zhǔn)都太“虛”。最直接、最落地的評價標(biāo)準(zhǔn)就是,當(dāng)用戶提出一個需求變更時,為了實現(xiàn)這個變更而修改軟件的成本越低,那么軟件的設(shè)計質(zhì)量就越高。當(dāng)來了一個需求變更時,怎樣才能讓修改軟件的成本降低呢?如果為了實現(xiàn)這個需求,需要修改 3 個模塊的代碼,完后這 3 個模塊都需要測試,其維護成本必然是“高”。那么怎樣才能降到最低呢?如果只需要修改 1 個模塊就可以實現(xiàn)這個需求,維護成本就要低很多了。
那么,怎樣才能在每次變更的時候都只修改一個模塊就能實現(xiàn)新需求呢?那就需要我們在平時就不斷地整理代碼,將那些因同一個原因而變更的代碼都放在一起,而將因不同原因而變更的代碼分開放,放在不同的模塊、不同的類中。這樣,當(dāng)因為這個原因而需要修改代碼時,需要修改的代碼都在這個模塊、這個類中,修改范圍就縮小了,維護成本降低了,修改代碼帶來的風(fēng)險自然也降低了,設(shè)計質(zhì)量也就提高了。
總之,單一職責(zé)原則要求我們在維護軟件的過程中需要不斷地進行整理,將軟件變化同一個原因的代碼放在一起,將軟件變化不同原因的代碼分開放。按照這樣的設(shè)計原則,回到前面那個案例中,那么應(yīng)當(dāng)怎樣去分析“付款”與“折扣”之間的關(guān)系呢?只需要回答兩個問題:
-
當(dāng)“付款”發(fā)生變更時,“折扣”是不是一定要變?
-
當(dāng)“折扣”發(fā)生變更時,“付款”是不是一定要變?
當(dāng)這兩個問題的答案是否定時,就說明“付款”與“折扣”是軟件變化的兩個不同的原因,那么把它們放在一起,放在同一個類、同一個方法中,合適嗎?不合適,就應(yīng)當(dāng)將“折扣”從“付款”中提取出來,單獨放在一個類中。
同樣的道理:
-
當(dāng)“限時折扣”發(fā)生變更的時候,“限量折扣”是不是一定要變?
-
當(dāng)“限量折扣”發(fā)生變更的時候,“某類商品的折扣”是不是一定要變?
-
……
最后發(fā)現(xiàn),不同類型的折扣也是軟件變化不同的原因。將它們放在同一個類、同一個方法中,合適嗎?通過以上分析,我們做出了如下設(shè)計:
在該設(shè)計中,將折扣功能從付款功能中獨立出去,做出了一個接口,然后以此為基礎(chǔ)設(shè)計了各種類型的折扣實現(xiàn)類。這樣的設(shè)計,當(dāng)付款功能發(fā)生變更時不會影響折扣,而折扣發(fā)生變更的時候不會影響付款。同樣,當(dāng)“限時折扣”發(fā)生變更時只與“限時折扣”有關(guān),“限量折扣”發(fā)生變更時也只與“限量折扣”有關(guān),與其他折扣類型無關(guān)。變更的范圍縮小了,維護成本就降低了,設(shè)計質(zhì)量提高了。這樣的設(shè)計就是“單一職責(zé)原則”的真諦。
接著,在這個版本的領(lǐng)域模型的基礎(chǔ)上進行程序設(shè)計,在設(shè)計時還可以加入一些設(shè)計模式的內(nèi)容,因此我們進行了如下的設(shè)計:
顯然,在該設(shè)計中加入了“策略模式”的內(nèi)容,將折扣功能做成了一個折扣策略接口與各種折扣策略的實現(xiàn)類。當(dāng)哪個折扣類型發(fā)生變更時就修改哪個折扣策略實現(xiàn)類;當(dāng)要增加新的類型的折扣時就再寫一個折扣策略實現(xiàn)類,設(shè)計質(zhì)量得到了提高。
VIP 會員的需求變更
在第一次變更的基礎(chǔ)上,很快迎來了第二次變更,這次是要增加 VIP 會員,業(yè)務(wù)需求如下。
增加 VIP 會員功能:
-
對不同類型的 VIP 會員(金卡會員、銀卡會員)進行不同的折扣;
-
在支付時,為 VIP 會員發(fā)放福利(積分、返券等);
-
VIP 會員可以享受某些特權(quán)。
我們拿到這樣的需求又應(yīng)當(dāng)怎樣設(shè)計呢?同樣,先回到領(lǐng)域模型,分析“用戶”與“VIP 會員”的關(guān)系,“付款”與“VIP 會員”的關(guān)系。在分析的時候,還是回答那兩個問題:
-
“用戶”發(fā)生變更時,“VIP 會員”是否要變;
-
“VIP 會員”發(fā)生變更時,“用戶”是否要變。
通過分析發(fā)現(xiàn),“用戶”與“VIP 會員”是兩個完全不同的事物。
-
“用戶”要做的是用戶的注冊、變更、注銷等操作;
-
“VIP 會員”要做的是會員折扣、會員福利與會員特權(quán);
-
而“付款”與“VIP 會員”的關(guān)系是在付款的過程中去調(diào)用會員折扣、會員福利與會員特權(quán)。
通過以上的分析,我們做出了以下版本的領(lǐng)域模型:
有了這些領(lǐng)域模型的變更,然后就可以以此作為基礎(chǔ),指導(dǎo)后面程序代碼的變更了。
支付方式的需求變更
同樣,第三次變更是增加更多的支付方式,我們在領(lǐng)域模型中分析“付款”與“支付方式”之間的關(guān)系,發(fā)現(xiàn)它們也是軟件變化不同的原因。因此,我們果斷做出了這樣的設(shè)計:
而在設(shè)計實現(xiàn)時,因為要與各個第三方的支付系統(tǒng)對接,也就是要與外部系統(tǒng)對接。為了使第三方的外部系統(tǒng)的變更對我們的影響最小化,在它們中間果斷加入了“適配器模式”,設(shè)計如下:
通過加入適配器模式,訂單 Service 在進行支付時調(diào)用的不再是外部的支付接口,而是“支付方式”接口,與外部系統(tǒng)解耦。只要保證“支付方式”接口是穩(wěn)定的,那么訂單 Service 就是穩(wěn)定的。比如:
-
當(dāng)支付寶支付接口發(fā)生變更時,影響的只限于支付寶 Adapter;
-
當(dāng)微信支付接口發(fā)生變更時,影響的只限于微信支付 Adapter;
-
當(dāng)要增加一個新的支付方式時,只需要再寫一個新的 Adapter。
日后不論哪種變更,要修改的代碼范圍縮小了,維護成本自然降低了,代碼質(zhì)量就提高了。
寫在最后
軟件發(fā)展的規(guī)律就是逐步由簡單軟件向復(fù)雜軟件轉(zhuǎn)變。簡單軟件有簡單軟件的設(shè)計,復(fù)雜軟件有復(fù)雜軟件的設(shè)計。因此,當(dāng)軟件由簡單軟件向復(fù)雜軟件轉(zhuǎn)變時,就需要通過兩頂帽子適時地對程序結(jié)構(gòu)進行調(diào)整,再實現(xiàn)新需求,只有這樣才能保證軟件不退化。然而,在變更的時候,如何調(diào)整代碼以適應(yīng)新的需求呢?
DDD 給了我們思路:在每次變更的時候,先回到領(lǐng)域模型,基于業(yè)務(wù)進行領(lǐng)域模型的變更。然后,再基于領(lǐng)域模型的變更,指導(dǎo)程序的變更。這樣,不論經(jīng)歷多少次需求變更,始終能夠保持設(shè)計質(zhì)量不退化。這樣的設(shè)計,才能保障系統(tǒng)始終在低成本的狀態(tài)下,可持續(xù)地不斷維護下去。
本文我們演練了如何運用 DDD 進行軟件的設(shè)計與變更,以及在設(shè)計與變更的過程中如何分析思考、如何評估代碼、如何實現(xiàn)高質(zhì)量。后續(xù)文章,我們將結(jié)合具體案例分析如何將領(lǐng)域模型的設(shè)計進一步落實到軟件系統(tǒng)的微服務(wù)設(shè)計與數(shù)據(jù)庫設(shè)計。
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺僅提供信息存儲服務(wù)。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!