<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          結(jié)合電商支付業(yè)務(wù),一文搞懂DDD

          共 8959字,需瀏覽 18分鐘

           ·

          2021-07-20 08:44


          源 / 頂級(jí)程序員        文/ 


          作者范鋼,曾任航天信息首席架構(gòu)師,《大話重構(gòu)》一書(shū)的作者。本文結(jié)合電商支付場(chǎng)景詳細(xì)描述了領(lǐng)域驅(qū)動(dòng)模型的實(shí)際應(yīng)用。


          2004 年,軟件大師 Eric Evans 的不朽著作《領(lǐng)域驅(qū)動(dòng)設(shè)計(jì):軟件核心復(fù)雜性應(yīng)對(duì)之道》面世,從書(shū)名可以看出,這是一本應(yīng)對(duì)軟件系統(tǒng)越來(lái)越復(fù)雜的方法論的圖書(shū)。然而,在當(dāng)時(shí),中國(guó)的軟件業(yè)才剛剛起步,軟件系統(tǒng)還沒(méi)有那么復(fù)雜,即使維護(hù)了幾年,軟件退化了,不好維護(hù)了,推倒重新開(kāi)發(fā)就好了。因此,在過(guò)去的那么多年里,真正運(yùn)用領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)開(kāi)發(fā)(DDD)的團(tuán)隊(duì)并不多。一套優(yōu)秀的方法論,因?yàn)楝F(xiàn)實(shí)階段的原因而一直不溫不火。

          不過(guò),這些年隨著中國(guó)軟件業(yè)的快速發(fā)展,軟件規(guī)模越來(lái)越大,生命周期也越來(lái)越長(zhǎng),推倒重新開(kāi)發(fā)的成本和風(fēng)險(xiǎn)越來(lái)越大。這時(shí),軟件團(tuán)隊(duì)急切需要在較低成本的狀態(tài)下持續(xù)維護(hù)一個(gè)系統(tǒng)很多年。然而,事與愿違。隨著時(shí)間的推移,程序越來(lái)越亂,維護(hù)成本越來(lái)越高,軟件退化成了無(wú)數(shù)軟件團(tuán)隊(duì)的噩夢(mèng)。

          這時(shí),微服務(wù)架構(gòu)成了規(guī)?;浖慕鉀Q之道。不過(guò),微服務(wù)對(duì)設(shè)計(jì)提出了很高的要求,強(qiáng)調(diào)“小而專(zhuān)、高內(nèi)聚”,否則就不能發(fā)揮出微服務(wù)的優(yōu)勢(shì),甚至可能令問(wèn)題更糟糕。

          因此,微服務(wù)的設(shè)計(jì),微服務(wù)的拆分都需要領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的指導(dǎo)。那么,領(lǐng)域驅(qū)動(dòng)為什么能解決軟件規(guī)?;膯?wèn)題呢?我們先從問(wèn)題的根源談起,即軟件退化。

          軟件退化的根源

          最近 10 年的互聯(lián)網(wǎng)發(fā)展,從電子商務(wù)到移動(dòng)互聯(lián),再到“互聯(lián)網(wǎng)+”與傳統(tǒng)行業(yè)的互聯(lián)網(wǎng)轉(zhuǎn)型,是一個(gè)非常痛苦的轉(zhuǎn)型過(guò)程。而近幾年的人工智能與 5G 技術(shù)的發(fā)展,又會(huì)帶動(dòng)整個(gè)產(chǎn)業(yè)向著大數(shù)據(jù)與物聯(lián)網(wǎng)發(fā)展,另一輪的技術(shù)轉(zhuǎn)型已經(jīng)拉開(kāi)帷幕。

          那么,在這個(gè)過(guò)程中,一方面會(huì)給我們帶來(lái)諸多的挑戰(zhàn),另一方面又會(huì)給我們帶來(lái)無(wú)盡的機(jī)會(huì),它會(huì)帶來(lái)更多的新興市場(chǎng)、新興產(chǎn)業(yè)與全新業(yè)務(wù),給我們帶來(lái)全新的發(fā)展機(jī)遇。

          然而,在面對(duì)全新業(yè)務(wù)、全新增長(zhǎng)點(diǎn)的時(shí)候,我們能不能把握住這樣的機(jī)遇呢?我們期望能把握住,但每次回到現(xiàn)實(shí),回到正在維護(hù)的系統(tǒng)時(shí),卻令人沮喪。我們的軟件總是經(jīng)歷著這樣的輪回,軟件設(shè)計(jì)質(zhì)量最高的時(shí)候是第一次設(shè)計(jì)的那個(gè)版本,當(dāng)?shù)谝粋€(gè)版本設(shè)計(jì)上線以后就開(kāi)始各種需求變更,這常常又會(huì)打亂原有的設(shè)計(jì)。

          因此,需求變更一次,版本迭代一次,軟件就修改一次,軟件修改一次,質(zhì)量就下降一次。不論第一次的設(shè)計(jì)質(zhì)量有多高,軟件經(jīng)歷不了幾次變更,就進(jìn)入一種低質(zhì)量、難以維護(hù)的狀態(tài)。進(jìn)而,團(tuán)隊(duì)就不得不在這樣的狀態(tài)下,以高成本的方式不斷地維護(hù)下去,維護(hù)很多年。

          這時(shí)候,維護(hù)好原有的業(yè)務(wù)都非常不易,又如何再去期望未來(lái)更多的全新業(yè)務(wù)呢?比如,這是一段電商網(wǎng)站支付功能的設(shè)計(jì),最初的版本設(shè)計(jì)質(zhì)量還是不錯(cuò)的:


          當(dāng)?shù)谝粋€(gè)版本上線以后,很快就迎來(lái)了第一次變更,變更的需求是增加商品折扣功能,并且這個(gè)折扣功能還要分為限時(shí)折扣、限量折扣、某類(lèi)商品的折扣、某個(gè)商品的折扣。當(dāng)我們拿到這個(gè)需求時(shí)怎么做呢?很簡(jiǎn)單,增加一個(gè) if 語(yǔ)句,if 限時(shí)折扣就怎么怎么樣,if 限量折扣就怎么怎么樣……代碼開(kāi)始膨脹了。

          接著,第二次變更需要增加 VIP 會(huì)員,除了增加各種金卡、銀卡的折扣,還要為會(huì)員發(fā)放各種福利,讓會(huì)員享受各種特權(quán)。為了實(shí)現(xiàn)這些需求,我們又要在 payoff() 方法中加入更多的代碼。

          第三次變更增加的是支付方式,除了支付寶支付,還要增加微信支付、各種銀行卡支付、各種支付平臺(tái)支付,此時(shí)又要塞入一大堆代碼。經(jīng)過(guò)這三次變更,你可以想象現(xiàn)在的 payoff() 方法是什么樣子了吧,變更是不是就可以結(jié)束了呢?其實(shí)不能,接著還要增加更多的秒殺、預(yù)訂、閃購(gòu)、眾籌,以及各種返券。程序變得越來(lái)越亂而難以閱讀和維護(hù),每次變更也變得越來(lái)越困難。


          問(wèn)題來(lái)了:為什么軟件會(huì)退化,會(huì)隨著變更而設(shè)計(jì)質(zhì)量下降呢?在這個(gè)問(wèn)題上,我們必須尋找到問(wèn)題的根源,才能對(duì)癥下藥、解決問(wèn)題。

          要探尋軟件退化的根源,先要從探尋軟件的本質(zhì)及其規(guī)律開(kāi)始,軟件的本質(zhì)就是對(duì)真實(shí)世界的模擬,每個(gè)軟件都能在真實(shí)世界中找到它的影子。因此,軟件中業(yè)務(wù)邏輯正確與否的唯一標(biāo)準(zhǔn)就是是否與真實(shí)世界一致。如果一致,則軟件是 OK 的;不一致,則用戶(hù)會(huì)提 Bug、提新需求。

          在這里發(fā)現(xiàn)了一個(gè)非常重要的線索,那就是,軟件要做成什么樣,既不由我們來(lái)決定,也不由用戶(hù)來(lái)決定,而是由客觀世界決定。用戶(hù)為什么總在改需求,是因?yàn)樗麄円膊淮_定客觀世界的規(guī)則,只有遇到問(wèn)題了他們才能想得起來(lái)。因此,對(duì)于我們來(lái)說(shuō),與其唯唯諾諾地按照用戶(hù)的要求去做軟件,不如在充分理解業(yè)務(wù)的基礎(chǔ)上去分析軟件,這樣會(huì)更有利于我們減少軟件維護(hù)的成本。

          那么,真實(shí)世界是怎樣的,我們就怎樣開(kāi)發(fā)軟件,不就簡(jiǎn)單了嗎?其實(shí)并非如此,因?yàn)檎鎸?shí)世界是非常復(fù)雜的,要深刻理解真實(shí)世界中的這些業(yè)務(wù)邏輯是需要一個(gè)過(guò)程的。因此,我們最初只能認(rèn)識(shí)真實(shí)世界中那些簡(jiǎn)單、清晰、易于理解的業(yè)務(wù)邏輯,把它們做到我們的軟件里,即每個(gè)軟件的第一個(gè)版本的需求總是那么清晰明了、易于設(shè)計(jì)。

          然而,當(dāng)我們把第一個(gè)版本的軟件交付用戶(hù)使用的時(shí)候,用戶(hù)卻會(huì)發(fā)現(xiàn),還有很多不簡(jiǎn)單、不明了、不易于理解的業(yè)務(wù)邏輯沒(méi)做到軟件里。這在使用軟件的過(guò)程中很不方便,和真實(shí)業(yè)務(wù)不一致,因此用戶(hù)就會(huì)提 Bug、提新需求。

          在我們不斷地修復(fù) Bug,實(shí)現(xiàn)新需求的過(guò)程中,軟件的業(yè)務(wù)邏輯也會(huì)越來(lái)越接近真實(shí)世界,使得我們的軟件越來(lái)越專(zhuān)業(yè),讓用戶(hù)感覺(jué)越來(lái)越好用。但是,在軟件越來(lái)越接近真實(shí)世界的過(guò)程中,業(yè)務(wù)邏輯就會(huì)變得越來(lái)越復(fù)雜,軟件規(guī)模也越來(lái)越龐大。

          你一定有這樣一個(gè)認(rèn)識(shí):簡(jiǎn)單軟件有簡(jiǎn)單軟件的設(shè)計(jì),復(fù)雜軟件有復(fù)雜軟件的設(shè)計(jì)。

          比如,現(xiàn)在的需求就是將用戶(hù)訂單按照“單價(jià) × 數(shù)量”公式來(lái)計(jì)算應(yīng)付金額,那么在一個(gè) PaymentBus 類(lèi)中增加一個(gè) payoff() 方法即可,這樣的設(shè)計(jì)沒(méi)有問(wèn)題。不過(guò),如果現(xiàn)在的需要在付款的過(guò)程中計(jì)算各種折扣、各種優(yōu)惠、各種返券,那么我們必然會(huì)做成一個(gè)復(fù)雜的程序結(jié)構(gòu)。


          但是,真實(shí)情況卻不是這樣的。真實(shí)情況是,起初我們拿到的需求是那個(gè)簡(jiǎn)單需求,然后在簡(jiǎn)單需求的基礎(chǔ)上進(jìn)行了設(shè)計(jì)開(kāi)發(fā)。但隨著軟件的不斷變更,軟件業(yè)務(wù)邏輯變得越來(lái)越復(fù)雜,軟件規(guī)模不斷擴(kuò)大,逐漸由一個(gè)簡(jiǎn)單軟件轉(zhuǎn)變成一個(gè)復(fù)雜軟件。

          這時(shí),如果要保持軟件設(shè)計(jì)質(zhì)量不退化,就應(yīng)當(dāng)逐步調(diào)整軟件的程序結(jié)構(gòu),逐漸由簡(jiǎn)單的程序結(jié)構(gòu)轉(zhuǎn)變?yōu)閺?fù)雜的程序結(jié)構(gòu)。如果我們總是這樣做,就能始終保持軟件的設(shè)計(jì)質(zhì)量,不過(guò)非常遺憾的是,我們以往在維護(hù)軟件的過(guò)程中卻不是這樣做的,而是不斷地在原有簡(jiǎn)單軟件的程序結(jié)構(gòu)下,往 payoff() 方法中塞代碼,這樣做必然會(huì)造成軟件的退化。

          也就是說(shuō),軟件退化的根源不是版本迭代和需求變更,版本迭代和需求變更只是一個(gè)誘因。如果每次軟件變更時(shí),適時(shí)地進(jìn)行解耦,進(jìn)行功能擴(kuò)展,再實(shí)現(xiàn)新的功能,就能保持高質(zhì)量的軟件設(shè)計(jì)。但如果在每次軟件變更時(shí)沒(méi)有調(diào)整程序結(jié)構(gòu),而是在原有的程序結(jié)構(gòu)上不斷地塞代碼,軟件就會(huì)退化。這就是軟件發(fā)展的規(guī)律,軟件退化的根源。

          杜絕軟件退化:兩頂帽子

          前面談到,要保持軟件設(shè)計(jì)質(zhì)量不退化,必須在每次需求變更的時(shí)候,對(duì)原有的程序結(jié)構(gòu)適當(dāng)?shù)剡M(jìn)行調(diào)整。那么應(yīng)當(dāng)怎樣進(jìn)行調(diào)整呢?還是回到前面電商網(wǎng)站付款功能的那個(gè)案例,看看每次需求變更應(yīng)當(dāng)怎樣設(shè)計(jì)。

          在交付第一個(gè)版本的基礎(chǔ)上,很快第一次需求變更就到來(lái)了。第一次需求變更的內(nèi)容如下。

          增加商品折扣功能,該功能分為以下幾種類(lèi)型:

          • 限時(shí)折扣

          • 限量折扣

          • 對(duì)某類(lèi)商品進(jìn)行折扣

          • 對(duì)某個(gè)商品進(jìn)行折扣

          • 不折扣

          以往我們拿到這個(gè)需求,就很不冷靜地開(kāi)始改代碼,修改成了如下一段代碼:


          這里增加了的 if else 語(yǔ)句,并不是一種好的變更方式。如果每次都這樣變更,那么軟件必然就會(huì)退化,進(jìn)入難以維護(hù)的狀態(tài)。這種變更為什么不好呢?因?yàn)樗`反了“開(kāi)放-封閉原則”。

          開(kāi)閉原則(OCP) 分為開(kāi)放原則與封閉原則兩部分。

          • 開(kāi)放原則:我們開(kāi)發(fā)的軟件系統(tǒng),對(duì)于功能擴(kuò)展是開(kāi)放的(Open for Extension),即當(dāng)系統(tǒng)需求發(fā)生變更時(shí),可以對(duì)軟件功能進(jìn)行擴(kuò)展,使其滿(mǎn)足用戶(hù)新的需求。

          • 封閉原則:對(duì)軟件代碼的修改應(yīng)當(dāng)是封閉的(Close for Modification),即在修改軟件的同時(shí),不要影響到系統(tǒng)原有的功能,所以應(yīng)當(dāng)在不修改原有代碼的基礎(chǔ)上實(shí)現(xiàn)新的功能。也就是說(shuō),在增加新功能的時(shí)候,新代碼與老代碼應(yīng)當(dāng)隔離,不能在同一個(gè)類(lèi)、同一個(gè)方法中。

          前面的設(shè)計(jì),在實(shí)現(xiàn)新功能的同時(shí),新代碼與老代碼在同一個(gè)類(lèi)、同一個(gè)方法中了,違反了“開(kāi)閉原則”。怎樣才能既滿(mǎn)足“開(kāi)閉原則”,又能夠?qū)崿F(xiàn)新功能呢?在原有的代碼上你發(fā)現(xiàn)什么都做不了!難道“開(kāi)閉原則”錯(cuò)了嗎?

          問(wèn)題的關(guān)鍵就在于,當(dāng)我們?cè)趯?shí)現(xiàn)新需求時(shí),應(yīng)當(dāng)采用“兩頂帽子”的方式進(jìn)行設(shè)計(jì),這種方式就要求在每次變更時(shí),將變更分為兩個(gè)步驟。

          兩頂帽子:

          • 在不添加新功能的前提下,重構(gòu)代碼,調(diào)整原有程序結(jié)構(gòu),以適應(yīng)新功能;
          • 實(shí)現(xiàn)新的功能。

          按以上案例為例,為了實(shí)現(xiàn)新的功能,我們?cè)谠写a的基礎(chǔ)上,在不添加新功能的前提下調(diào)整原有程序結(jié)構(gòu),我們抽取出了 Strategy 這樣一個(gè)接口和“不折扣”這個(gè)實(shí)現(xiàn)類(lèi)。這時(shí),原有程序變了嗎?沒(méi)有。但是程序結(jié)構(gòu)卻變了,增加了這樣一個(gè)接口,稱(chēng)之為“可擴(kuò)展點(diǎn)”。在這個(gè)可擴(kuò)展點(diǎn)的基礎(chǔ)上再實(shí)現(xiàn)各種折扣,既能滿(mǎn)足“開(kāi)放-封閉原則”來(lái)保證程序質(zhì)量,又能夠滿(mǎn)足新的需求。當(dāng)日后發(fā)生新的變更時(shí),什么類(lèi)型的折扣有變化就修改哪個(gè)實(shí)現(xiàn)類(lèi),添加新的折扣類(lèi)型就增加新的實(shí)現(xiàn)類(lèi),維護(hù)成本得到降低。


          “兩頂帽子”的設(shè)計(jì)方式意義重大。過(guò)去,我們每次在設(shè)計(jì)軟件時(shí)總是擔(dān)心日后的變更,就很不冷靜地設(shè)計(jì)了很多所謂的“靈活設(shè)計(jì)”。然而,每一種“靈活設(shè)計(jì)”只能應(yīng)對(duì)一種需求變更,而我們又不是先知,不知道日后會(huì)發(fā)生什么樣的變更。最后的結(jié)果就是,我們期望的變更并沒(méi)有發(fā)生,所做的設(shè)計(jì)都變成了擺設(shè),它既不起什么作用,還增加了程序復(fù)雜度;我們沒(méi)有期望的變更發(fā)生了,原有的程序依然不能解決新的需求,程序又被打回了原形。因此,這樣的設(shè)計(jì)不能真正解決未來(lái)變更的問(wèn)題,被稱(chēng)為“過(guò)度設(shè)計(jì)”。

          有了“兩頂帽子”,我們不再需要焦慮,不再需要過(guò)度設(shè)計(jì),正確的思路應(yīng)當(dāng)是“活在今天的格子里做今天的事兒”,也就是為當(dāng)前的需求進(jìn)行設(shè)計(jì),使其剛剛滿(mǎn)足當(dāng)前的需求。所謂的“高質(zhì)量的軟件設(shè)計(jì)”就是要掌握一個(gè)平衡,一方面要滿(mǎn)足當(dāng)前的需求,另一方面要讓設(shè)計(jì)剛剛滿(mǎn)足需求,從而使設(shè)計(jì)最簡(jiǎn)化、代碼最少。這樣做,不僅軟件設(shè)計(jì)質(zhì)量提高了,設(shè)計(jì)難點(diǎn)也得到了大幅度降低。

          簡(jiǎn)而言之,保持軟件設(shè)計(jì)不退化的關(guān)鍵在于每次需求變更的設(shè)計(jì),只有保證每次需求變更時(shí)做出正確的設(shè)計(jì),才能保證軟件以一種良性循環(huán)的方式不斷維護(hù)下去。這種正確的設(shè)計(jì)方式就是“兩頂帽子”。

          但是,在實(shí)踐“兩頂帽子”的過(guò)程中,比較困難的是第一步。在不添加新功能的前提下,如何重構(gòu)代碼,如何調(diào)整原有程序結(jié)構(gòu),以適應(yīng)新功能,這是有難度的。很多時(shí)候,第一次變更、第二次變更、第三次變更,這些事情還能想清楚;但經(jīng)歷了第十次變更、第二十次變更、第三十次變更,這些事情就想不清楚了,設(shè)計(jì)開(kāi)始迷失方向。

          那么,有沒(méi)有一種方法,讓我們?cè)诘谑巫兏?、第二十次變更、第三十次變更時(shí),依然能夠找到正確的設(shè)計(jì)呢?有,那就是“領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)”。

          保持軟件質(zhì)量:領(lǐng)域驅(qū)動(dòng)

          前面談到,軟件的本質(zhì)就是對(duì)真實(shí)世界的模擬。因此,我們會(huì)有一種想法,能不能將軟件設(shè)計(jì)與真實(shí)世界對(duì)應(yīng)起來(lái),真實(shí)世界是什么樣子,那么軟件世界就怎么設(shè)計(jì)。如果是這樣的話,那么在每次需求變更時(shí),將變更還原到真實(shí)世界中,看看真實(shí)世界是什么樣子的,根據(jù)真實(shí)世界進(jìn)行變更。這樣,日后不論怎么變更,經(jīng)過(guò)多少輪變更,都按照這樣的方法進(jìn)行設(shè)計(jì),就不會(huì)迷失方向,設(shè)計(jì)質(zhì)量就可以得到保證,這就是“領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)”的思想。

          那么,如何將真實(shí)世界與軟件世界對(duì)應(yīng)起來(lái)呢?這樣的對(duì)應(yīng)就包括以下三個(gè)方面的內(nèi)容:

          • 真實(shí)世界有什么事物,軟件世界就有什么對(duì)象;

          • 真實(shí)世界中這些事物都有哪些行為,軟件世界中這些對(duì)象就有哪些方法;

          • 真實(shí)世界中這些事物間都有哪些關(guān)系,軟件世界中這些對(duì)象間就有什么關(guān)聯(lián)。


          真實(shí)世界與軟件世界的對(duì)應(yīng)圖

          在領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)中,就將以上三個(gè)對(duì)應(yīng),先做成一個(gè)領(lǐng)域模型,然后通過(guò)這個(gè)領(lǐng)域模型指導(dǎo)程序設(shè)計(jì);在每次需求變更時(shí),先將需求還原到領(lǐng)域模型中分析,根據(jù)領(lǐng)域模型背后的真實(shí)世界進(jìn)行變更,然后根據(jù)領(lǐng)域模型的變更指導(dǎo)軟件的變更,設(shè)計(jì)質(zhì)量就可以得到提高。


          結(jié)合電商支付實(shí)際演練DDD


          現(xiàn)在,我們以電商網(wǎng)站的支付功能為例,來(lái)演練一下基于 DDD 的軟件設(shè)計(jì)及其變更的過(guò)程。

          運(yùn)用 DDD 進(jìn)行軟件設(shè)計(jì)

          開(kāi)發(fā)人員在最開(kāi)始收到的關(guān)于用戶(hù)付款功能的需求描述是這樣的:

          • 在用戶(hù)下單以后,經(jīng)過(guò)下單流程進(jìn)入付款功能;

          • 通過(guò)用戶(hù)檔案獲得用戶(hù)名稱(chēng)、地址等信息;

          • 記錄商品及其數(shù)量,并匯總付款金額;

          • 保存訂單;

          • 通過(guò)遠(yuǎn)程調(diào)用支付接口進(jìn)行支付。

          以往當(dāng)拿到這個(gè)需求時(shí),開(kāi)發(fā)人員往往草草設(shè)計(jì)以后就開(kāi)始編碼,設(shè)計(jì)質(zhì)量也就不高。

          而采用領(lǐng)域驅(qū)動(dòng)的方式,在拿到新需求以后,應(yīng)當(dāng)先進(jìn)行需求分析,設(shè)計(jì)領(lǐng)域模型。按照以上業(yè)務(wù)場(chǎng)景,可以分析出:

          • 該場(chǎng)景中有“訂單”,每個(gè)訂單都對(duì)應(yīng)一個(gè)用戶(hù);

          • 一個(gè)用戶(hù)可以有多個(gè)用戶(hù)地址,但每個(gè)訂單只能有一個(gè)用戶(hù)地址;

          • 此外,一個(gè)訂單對(duì)應(yīng)多個(gè)訂單明細(xì),每個(gè)訂單明細(xì)對(duì)應(yīng)一個(gè)商品,每個(gè)商品對(duì)應(yīng)一個(gè)供應(yīng)商。

          最后,我們對(duì)訂單可以進(jìn)行“下單”“付款”“查看訂單狀態(tài)”等操作。因此形成了以下領(lǐng)域模型圖:


          有了這樣的領(lǐng)域模型,就可以通過(guò)該模型進(jìn)行以下程序設(shè)計(jì):


          通過(guò)領(lǐng)域模型的指導(dǎo),將“訂單”分為訂單 Service 與值對(duì)象,將“用戶(hù)”分為用戶(hù) Service 與值對(duì)象,將“商品”分為商品 Service 與值對(duì)象……然后,在此基礎(chǔ)上實(shí)現(xiàn)各自的方法。

          商品折扣的需求變更

          當(dāng)電商網(wǎng)站的付款功能按照領(lǐng)域模型完成了第一個(gè)版本的設(shè)計(jì)后,很快就迎來(lái)了第一次需求變更,即增加折扣功能,并且該折扣功能分為限時(shí)折扣、限量折扣、某類(lèi)商品的折扣、某個(gè)商品的折扣與不折扣。當(dāng)我們拿到這個(gè)需求時(shí)應(yīng)當(dāng)怎樣設(shè)計(jì)呢?很顯然,在 payoff() 方法中去插入 if else 語(yǔ)句是不 OK 的。這時(shí),按照領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的思想,應(yīng)當(dāng)將需求變更還原到領(lǐng)域模型中進(jìn)行分析,進(jìn)而根據(jù)領(lǐng)域模型背后的真實(shí)世界進(jìn)行變更。


          這是上一個(gè)版本的領(lǐng)域模型,現(xiàn)在我們要在這個(gè)模型的基礎(chǔ)上增加折扣功能,并且還要分為限時(shí)折扣、限量折扣、某類(lèi)商品的折扣等不同類(lèi)型。這時(shí),我們應(yīng)當(dāng)怎么分析設(shè)計(jì)呢?

          首先要分析付款與折扣的關(guān)系。

          付款與折扣是什么關(guān)系呢?你可能會(huì)認(rèn)為折扣是在付款的過(guò)程中進(jìn)行的折扣,因此就應(yīng)當(dāng)將折扣寫(xiě)到付款中。這樣思考對(duì)嗎?我們應(yīng)當(dāng)基于什么樣的思想與原則來(lái)設(shè)計(jì)呢?這時(shí),另外一個(gè)重量級(jí)的設(shè)計(jì)原則應(yīng)該出場(chǎng)了,那就是“單一職責(zé)原則”。

          單一職責(zé)原則:軟件系統(tǒng)中的每個(gè)元素只完成自己職責(zé)范圍內(nèi)的事,而將其他的事交給別人去做,我只是去調(diào)用。

          單一職責(zé)原則是軟件設(shè)計(jì)中一個(gè)非常重要的原則,但如何正確地理解它成為一個(gè)非常關(guān)鍵的問(wèn)題。在這句話中,準(zhǔn)確理解的關(guān)鍵就在于“職責(zé)”二字,即自己職責(zé)的范圍到底在哪里。以往,我們錯(cuò)誤地理解這個(gè)“職責(zé)”就是做某一個(gè)事,與這個(gè)事情相關(guān)的所有事情都是它的職責(zé),正因?yàn)檫@個(gè)錯(cuò)誤的理解,帶來(lái)了許多錯(cuò)誤的設(shè)計(jì),而將折扣寫(xiě)到付款功能中。那么,怎樣才是對(duì)“職責(zé)”正確的理解呢?

          “一個(gè)職責(zé)就是軟件變化的一個(gè)原因”是著名的軟件大師 Bob 大叔在他的《敏捷軟件開(kāi)發(fā):原則、模式與實(shí)踐》中的表述。但這個(gè)表述過(guò)于精簡(jiǎn),很難深刻地理解其中的內(nèi)涵。這里我好好解讀一下這句話。

          先思考一下什么是高質(zhì)量的代碼?你可能立即會(huì)想到“低耦合、高內(nèi)聚”,以及各種設(shè)計(jì)原則,但這些評(píng)價(jià)標(biāo)準(zhǔn)都太“虛”。最直接、最落地的評(píng)價(jià)標(biāo)準(zhǔn)就是,當(dāng)用戶(hù)提出一個(gè)需求變更時(shí),為了實(shí)現(xiàn)這個(gè)變更而修改軟件的成本越低,那么軟件的設(shè)計(jì)質(zhì)量就越高。當(dāng)來(lái)了一個(gè)需求變更時(shí),怎樣才能讓修改軟件的成本降低呢?如果為了實(shí)現(xiàn)這個(gè)需求,需要修改 3 個(gè)模塊的代碼,完后這 3 個(gè)模塊都需要測(cè)試,其維護(hù)成本必然是“高”。那么怎樣才能降到最低呢?如果只需要修改 1 個(gè)模塊就可以實(shí)現(xiàn)這個(gè)需求,維護(hù)成本就要低很多了。

          那么,怎樣才能在每次變更的時(shí)候都只修改一個(gè)模塊就能實(shí)現(xiàn)新需求呢?那就需要我們?cè)谄綍r(shí)就不斷地整理代碼,將那些因同一個(gè)原因而變更的代碼都放在一起,而將因不同原因而變更的代碼分開(kāi)放,放在不同的模塊、不同的類(lèi)中。這樣,當(dāng)因?yàn)檫@個(gè)原因而需要修改代碼時(shí),需要修改的代碼都在這個(gè)模塊、這個(gè)類(lèi)中,修改范圍就縮小了,維護(hù)成本降低了,修改代碼帶來(lái)的風(fēng)險(xiǎn)自然也降低了,設(shè)計(jì)質(zhì)量也就提高了。

          總之,單一職責(zé)原則要求我們?cè)诰S護(hù)軟件的過(guò)程中需要不斷地進(jìn)行整理,將軟件變化同一個(gè)原因的代碼放在一起,將軟件變化不同原因的代碼分開(kāi)放。按照這樣的設(shè)計(jì)原則,回到前面那個(gè)案例中,那么應(yīng)當(dāng)怎樣去分析“付款”與“折扣”之間的關(guān)系呢?只需要回答兩個(gè)問(wèn)題:

          • 當(dāng)“付款”發(fā)生變更時(shí),“折扣”是不是一定要變?

          • 當(dāng)“折扣”發(fā)生變更時(shí),“付款”是不是一定要變?

          當(dāng)這兩個(gè)問(wèn)題的答案是否定時(shí),就說(shuō)明“付款”與“折扣”是軟件變化的兩個(gè)不同的原因,那么把它們放在一起,放在同一個(gè)類(lèi)、同一個(gè)方法中,合適嗎?不合適,就應(yīng)當(dāng)將“折扣”從“付款”中提取出來(lái),單獨(dú)放在一個(gè)類(lèi)中。

          同樣的道理:

          • 當(dāng)“限時(shí)折扣”發(fā)生變更的時(shí)候,“限量折扣”是不是一定要變?

          • 當(dāng)“限量折扣”發(fā)生變更的時(shí)候,“某類(lèi)商品的折扣”是不是一定要變?

          • ……

          最后發(fā)現(xiàn),不同類(lèi)型的折扣也是軟件變化不同的原因。將它們放在同一個(gè)類(lèi)、同一個(gè)方法中,合適嗎?通過(guò)以上分析,我們做出了如下設(shè)計(jì):


          在該設(shè)計(jì)中,將折扣功能從付款功能中獨(dú)立出去,做出了一個(gè)接口,然后以此為基礎(chǔ)設(shè)計(jì)了各種類(lèi)型的折扣實(shí)現(xiàn)類(lèi)。這樣的設(shè)計(jì),當(dāng)付款功能發(fā)生變更時(shí)不會(huì)影響折扣,而折扣發(fā)生變更的時(shí)候不會(huì)影響付款。同樣,當(dāng)“限時(shí)折扣”發(fā)生變更時(shí)只與“限時(shí)折扣”有關(guān),“限量折扣”發(fā)生變更時(shí)也只與“限量折扣”有關(guān),與其他折扣類(lèi)型無(wú)關(guān)。變更的范圍縮小了,維護(hù)成本就降低了,設(shè)計(jì)質(zhì)量提高了。這樣的設(shè)計(jì)就是“單一職責(zé)原則”的真諦。

          接著,在這個(gè)版本的領(lǐng)域模型的基礎(chǔ)上進(jìn)行程序設(shè)計(jì),在設(shè)計(jì)時(shí)還可以加入一些設(shè)計(jì)模式的內(nèi)容,因此我們進(jìn)行了如下的設(shè)計(jì):


          顯然,在該設(shè)計(jì)中加入了“策略模式”的內(nèi)容,將折扣功能做成了一個(gè)折扣策略接口與各種折扣策略的實(shí)現(xiàn)類(lèi)。當(dāng)哪個(gè)折扣類(lèi)型發(fā)生變更時(shí)就修改哪個(gè)折扣策略實(shí)現(xiàn)類(lèi);當(dāng)要增加新的類(lèi)型的折扣時(shí)就再寫(xiě)一個(gè)折扣策略實(shí)現(xiàn)類(lèi),設(shè)計(jì)質(zhì)量得到了提高。

          VIP 會(huì)員的需求變更

          在第一次變更的基礎(chǔ)上,很快迎來(lái)了第二次變更,這次是要增加 VIP 會(huì)員,業(yè)務(wù)需求如下。

          增加 VIP 會(huì)員功能:

          • 對(duì)不同類(lèi)型的 VIP 會(huì)員(金卡會(huì)員、銀卡會(huì)員)進(jìn)行不同的折扣;

          • 在支付時(shí),為 VIP 會(huì)員發(fā)放福利(積分、返券等);

          • VIP 會(huì)員可以享受某些特權(quán)。

          我們拿到這樣的需求又應(yīng)當(dāng)怎樣設(shè)計(jì)呢?同樣,先回到領(lǐng)域模型,分析“用戶(hù)”與“VIP 會(huì)員”的關(guān)系,“付款”與“VIP 會(huì)員”的關(guān)系。在分析的時(shí)候,還是回答那兩個(gè)問(wèn)題:

          • “用戶(hù)”發(fā)生變更時(shí),“VIP 會(huì)員”是否要變;

          • “VIP 會(huì)員”發(fā)生變更時(shí),“用戶(hù)”是否要變。

          通過(guò)分析發(fā)現(xiàn),“用戶(hù)”與“VIP 會(huì)員”是兩個(gè)完全不同的事物。

          • “用戶(hù)”要做的是用戶(hù)的注冊(cè)、變更、注銷(xiāo)等操作;

          • “VIP 會(huì)員”要做的是會(huì)員折扣、會(huì)員福利與會(huì)員特權(quán);

          • 而“付款”與“VIP 會(huì)員”的關(guān)系是在付款的過(guò)程中去調(diào)用會(huì)員折扣、會(huì)員福利與會(huì)員特權(quán)。

          通過(guò)以上的分析,我們做出了以下版本的領(lǐng)域模型:


          有了這些領(lǐng)域模型的變更,然后就可以以此作為基礎(chǔ),指導(dǎo)后面程序代碼的變更了。

          支付方式的需求變更

          同樣,第三次變更是增加更多的支付方式,我們?cè)陬I(lǐng)域模型中分析“付款”與“支付方式”之間的關(guān)系,發(fā)現(xiàn)它們也是軟件變化不同的原因。因此,我們果斷做出了這樣的設(shè)計(jì):


          而在設(shè)計(jì)實(shí)現(xiàn)時(shí),因?yàn)橐c各個(gè)第三方的支付系統(tǒng)對(duì)接,也就是要與外部系統(tǒng)對(duì)接。為了使第三方的外部系統(tǒng)的變更對(duì)我們的影響最小化,在它們中間果斷加入了“適配器模式”,設(shè)計(jì)如下:


          通過(guò)加入適配器模式,訂單 Service 在進(jìn)行支付時(shí)調(diào)用的不再是外部的支付接口,而是“支付方式”接口,與外部系統(tǒng)解耦。只要保證“支付方式”接口是穩(wěn)定的,那么訂單 Service 就是穩(wěn)定的。比如:

          • 當(dāng)支付寶支付接口發(fā)生變更時(shí),影響的只限于支付寶 Adapter;

          • 當(dāng)微信支付接口發(fā)生變更時(shí),影響的只限于微信支付 Adapter;

          • 當(dāng)要增加一個(gè)新的支付方式時(shí),只需要再寫(xiě)一個(gè)新的 Adapter。

          日后不論哪種變更,要修改的代碼范圍縮小了,維護(hù)成本自然降低了,代碼質(zhì)量就提高了。

          寫(xiě)在最后



          軟件發(fā)展的規(guī)律就是逐步由簡(jiǎn)單軟件向復(fù)雜軟件轉(zhuǎn)變。簡(jiǎn)單軟件有簡(jiǎn)單軟件的設(shè)計(jì),復(fù)雜軟件有復(fù)雜軟件的設(shè)計(jì)。因此,當(dāng)軟件由簡(jiǎn)單軟件向復(fù)雜軟件轉(zhuǎn)變時(shí),就需要通過(guò)兩頂帽子適時(shí)地對(duì)程序結(jié)構(gòu)進(jìn)行調(diào)整,再實(shí)現(xiàn)新需求,只有這樣才能保證軟件不退化。然而,在變更的時(shí)候,如何調(diào)整代碼以適應(yīng)新的需求呢?

          DDD 給了我們思路:在每次變更的時(shí)候,先回到領(lǐng)域模型,基于業(yè)務(wù)進(jìn)行領(lǐng)域模型的變更。然后,再基于領(lǐng)域模型的變更,指導(dǎo)程序的變更。這樣,不論經(jīng)歷多少次需求變更,始終能夠保持設(shè)計(jì)質(zhì)量不退化。這樣的設(shè)計(jì),才能保障系統(tǒng)始終在低成本的狀態(tài)下,可持續(xù)地不斷維護(hù)下去。

          本文我們演練了如何運(yùn)用 DDD 進(jìn)行軟件的設(shè)計(jì)與變更,以及在設(shè)計(jì)與變更的過(guò)程中如何分析思考、如何評(píng)估代碼、如何實(shí)現(xiàn)高質(zhì)量。后續(xù)文章,我們將結(jié)合具體案例分析如何將領(lǐng)域模型的設(shè)計(jì)進(jìn)一步落實(shí)到軟件系統(tǒng)的微服務(wù)設(shè)計(jì)與數(shù)據(jù)庫(kù)設(shè)計(jì)。

          好文推薦

          公司起訴技術(shù)總監(jiān)索賠 90 萬(wàn)元,稱(chēng)其拖延研發(fā)進(jìn)度


          網(wǎng)傳京東某程序員因壓力太大,在商品頁(yè)面置入罵人代碼!京東辟謠:不關(guān)我們的事,外部商家干的!


          某大廠程序員炫耀:來(lái)新加坡后,每天最多工作五六個(gè)小時(shí),家庭年收入150萬(wàn)人民幣,已躺平!






          一鍵三連「分享」、「點(diǎn)贊」和「在看」

          技術(shù)干貨與你天天見(jiàn)~



          瀏覽 20
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲爱爱视频 | 四虎亚洲影院 | www.91AV在线免费观看 | 欧美大屌网站 | 大香蕉这里是精品12 |