現(xiàn)實(shí)世界中的業(yè)務(wù)邏輯,在 IT 系統(tǒng)業(yè)務(wù)分析時(shí),適合某個(gè)行業(yè)和領(lǐng)域相關(guān)的,所以又叫做領(lǐng)域。領(lǐng)域,指的特定行業(yè)或者場(chǎng)景下的業(yè)務(wù)邏輯。DDD 中的模型是指反應(yīng) IT 系統(tǒng)的業(yè)務(wù)邏輯和狀態(tài)的對(duì)象,是從具體業(yè)務(wù)(領(lǐng)域)中提取出來(lái)的,因此又叫做領(lǐng)域模型。通過(guò)對(duì)實(shí)際業(yè)務(wù)出發(fā),而非馬上關(guān)注數(shù)據(jù)庫(kù)、程序設(shè)計(jì)。通過(guò)識(shí)別出固定的模式,并將這些業(yè)務(wù)邏輯的承載者抽象到一個(gè)模型上。這個(gè)模型負(fù)責(zé)處理業(yè)務(wù)邏輯,并表達(dá)當(dāng)前的系統(tǒng)狀態(tài)。這個(gè)過(guò)程就是領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)。我們做的計(jì)算機(jī)系統(tǒng)實(shí)際上,是替代了現(xiàn)實(shí)世界中的一些操作。按照面向?qū)ο笤O(shè)計(jì)的話,我們的系統(tǒng)是一個(gè)電子餐廳?,F(xiàn)實(shí)餐廳中的實(shí)體,應(yīng)該對(duì)應(yīng)到我們的系統(tǒng)中去,用于承載業(yè)務(wù),例如收銀員、顧客、廚師、餐桌、菜品,這些虛擬的實(shí)體表達(dá)了系統(tǒng)的狀態(tài),在某種程度上就能指代系統(tǒng),這就是模型,如果找到了這些元素,就很容易設(shè)計(jì)出軟件。后來(lái),如果我什么業(yè)務(wù)邏輯想不清楚,我就會(huì)把電斷掉,假裝自己是服務(wù)員,用紙和筆走一邊業(yè)務(wù)流程。分析業(yè)務(wù),設(shè)計(jì)領(lǐng)域模型,編寫(xiě)代碼。這就是領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的基本過(guò)程。隨后會(huì)介紹,如何設(shè)計(jì)領(lǐng)域模型,當(dāng)我們建立了領(lǐng)域模型后,我可以考慮使用領(lǐng)域模型指導(dǎo)開(kāi)發(fā)工作。指導(dǎo)數(shù)據(jù)庫(kù)設(shè)計(jì) 指導(dǎo)模塊分包和代碼設(shè)計(jì) 指導(dǎo) RESTful API 設(shè)計(jì) 指導(dǎo)事務(wù)策略 指導(dǎo)權(quán)限 在我們之前的例子中,收銀員需要負(fù)責(zé)處理收銀的操作,同時(shí)表達(dá)這個(gè)餐廳有收營(yíng)員這樣的一個(gè)狀態(tài)。收營(yíng)員收到錢并記錄到賬本中,賬本負(fù)責(zé)處理記錄錢的業(yè)務(wù)邏輯,同時(shí)表達(dá)系統(tǒng)中有多少錢的狀態(tài)。
分析領(lǐng)域模型時(shí),請(qǐng)把”電“斷掉 我們進(jìn)行業(yè)務(wù)系統(tǒng)開(kāi)發(fā)時(shí),大多數(shù)人都會(huì)認(rèn)同一個(gè)觀點(diǎn):將業(yè)務(wù)和模型設(shè)計(jì)清楚之后,開(kāi)發(fā)起來(lái)會(huì)容易很多。但是實(shí)際開(kāi)發(fā)過(guò)程中,我們既要分析業(yè)務(wù),也要處理一些技術(shù)細(xì)節(jié),例如:如何響應(yīng)表單提交、如何存儲(chǔ)到數(shù)據(jù)庫(kù)、事務(wù)該怎么處理等。使用領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)還有一個(gè)好處,我們可以通過(guò)隔離這些技術(shù)細(xì)節(jié),先進(jìn)行業(yè)務(wù)邏輯建模,然后再完成技術(shù)實(shí)現(xiàn),因?yàn)闃I(yè)務(wù)模型已經(jīng)建立,技術(shù)細(xì)節(jié)無(wú)非就是響應(yīng)用戶操作和持久化模型。我們可以吧系統(tǒng)復(fù)雜的問(wèn)題分為兩類:(分離技術(shù)復(fù)雜度和業(yè)務(wù)復(fù)雜度) 技術(shù)復(fù)雜度,軟件設(shè)計(jì)中和技術(shù)實(shí)現(xiàn)相關(guān)的問(wèn)題,例如處理用戶輸入,持久化模型,處理網(wǎng)絡(luò)通信等。業(yè)務(wù)復(fù)雜度,軟件設(shè)計(jì)中和業(yè)務(wù)邏輯相關(guān)的問(wèn)題,例如為訂單添加商品,需要計(jì)算訂單總價(jià),應(yīng)用折扣規(guī)則等。當(dāng)我們分析業(yè)務(wù)并建模時(shí),過(guò)于關(guān)注技術(shù)實(shí)現(xiàn),會(huì)帶來(lái)極大的干擾。我學(xué)到最實(shí)用的思維方法,就是在這個(gè)過(guò)程把”電“斷掉,技術(shù)復(fù)雜度中的用戶交互想象成人工交談,持久化想象成用紙和筆記錄。DDD 還強(qiáng)調(diào),業(yè)務(wù)建模應(yīng)該充分的和業(yè)務(wù)專家在一起,不應(yīng)該只是實(shí)現(xiàn)軟件的工程師自嗨。業(yè)務(wù)專家是一個(gè)虛擬的角色,有可能是一線業(yè)務(wù)人員、項(xiàng)目經(jīng)理、或者軟件工程師。由于和業(yè)務(wù)專家一起完成建模,因此盡量不要選用非常專業(yè)的繪圖的工具和使用技術(shù)語(yǔ)言。DDD 只是一種建模思想,并沒(méi)有規(guī)定使用的具體工具。我這里使用 PPT 的線條和形狀,用 E-R 的方式表達(dá)領(lǐng)域模型,如果大家都很熟悉 UML 也是可以的。甚至實(shí)際工作中,我們大量使用便利貼和白板完成建模工作。這個(gè)建模過(guò)程可以是技術(shù)人員和業(yè)務(wù)專家一起討論出來(lái),也可以是使用 ”事件風(fēng)暴“ 這類工作坊的方式完成。這個(gè)過(guò)程非常重要,DDD 把這個(gè)過(guò)程稱作 協(xié)作設(shè)計(jì)。通過(guò)這個(gè)過(guò)程,我們得到了領(lǐng)域模型。(原始領(lǐng)域模型) 上圖使我們通過(guò)業(yè)務(wù)分析得到的一個(gè)非常基本的領(lǐng)域模型,我們的點(diǎn)餐系統(tǒng)中,會(huì)有座位、訂單、菜品、評(píng)價(jià) 幾個(gè)模型。一個(gè)座位可以由多個(gè)訂單,每個(gè)訂單可以有多個(gè)菜品和評(píng)價(jià)。同時(shí),菜品也會(huì)被不同的訂單使用。
上下文、二義性、統(tǒng)一語(yǔ)言 我們用這個(gè)模型開(kāi)發(fā)系統(tǒng),使用領(lǐng)域模型驅(qū)動(dòng)的方式開(kāi)發(fā),相對(duì)于事務(wù)腳本的方式,已經(jīng)容易和清晰很多了,但還是有一些問(wèn)題。有一天,市場(chǎng)告訴我們,這個(gè)系統(tǒng)會(huì)有一個(gè)邏輯問(wèn)題。就是系統(tǒng)中菜品被刪除,訂單也不能查看。在我們之前的認(rèn)知里面,訂單和菜品是一個(gè)多對(duì)多的關(guān)系,菜品都不存在了,這個(gè)訂單還有什么用。菜品,在這里存在了致命的二義性?。?!這里的菜品實(shí)際上有兩個(gè)含義:菜品管理中的菜品下架后,不應(yīng)該產(chǎn)生新的訂單,同時(shí)也不應(yīng)該對(duì)訂單中的菜品造成任何影響。這些問(wèn)題是因?yàn)?,技術(shù)專家和業(yè)務(wù)專家的語(yǔ)言沒(méi)有統(tǒng)一, DDD 認(rèn)識(shí)到了這個(gè)問(wèn)題,統(tǒng)一語(yǔ)言是實(shí)現(xiàn)良好的領(lǐng)域模型的前提,因此應(yīng)該 ”大聲的建模“。我在參與這個(gè)過(guò)程目睹過(guò)大量有意義的爭(zhēng)吵,正是這些爭(zhēng)吵讓領(lǐng)域模型變得原來(lái)越清晰。這個(gè)過(guò)程叫做統(tǒng)一語(yǔ)言。(領(lǐng)域模型v2) 和現(xiàn)實(shí)生活中一樣,產(chǎn)生二義性的原因是因?yàn)槲覀兊膶?duì)話發(fā)生在不同的上下文中,我們?cè)谡勔粋€(gè)概念必須在確定的上下文中才有意義。在不同的場(chǎng)景下,即使使用的詞匯相同,但是業(yè)務(wù)邏輯本質(zhì)都是不同的。想象一下,發(fā)生在《武林外傳》中同福客棧的幾段對(duì)話。(對(duì)話) 這段對(duì)話中實(shí)際上有三個(gè)上下文,這里的 ”菜“ 這個(gè)詞出現(xiàn)了三次,但是實(shí)際上業(yè)務(wù)含義完全不同。大嘴說(shuō)去買菜,這里的菜被抽象出來(lái)應(yīng)該是食材采購(gòu)品,如果掌柜對(duì)這個(gè)菜進(jìn)行管理,應(yīng)該具有采購(gòu)者、名稱、采購(gòu)商家、采購(gòu)價(jià)等。 秀才說(shuō)實(shí)習(xí)生把賬單中的菜算錯(cuò)了價(jià)格,秀才需要對(duì)賬單進(jìn)行管理,這里的菜應(yīng)該指的賬單科目,現(xiàn)實(shí)中一般是會(huì)計(jì)科目。 - 老白說(shuō)的客人點(diǎn)了一個(gè)醬鴨,這里老白關(guān)注的是訂單下面的訂單項(xiàng),訂單項(xiàng)包含的屬性有價(jià)格、數(shù)量、小計(jì)、折扣等信息。
實(shí)際上,還有一個(gè)隱藏的模型——上架中商品。掌柜需要添加菜品到菜單中,客人才能點(diǎn),這個(gè)商品就是我們平時(shí)一般概念上的商品。我們把語(yǔ)言再次統(tǒng)一,得到新的模型。(領(lǐng)域模型v3) 4個(gè)被紅色虛線框起來(lái)的區(qū)域中,我們都可以使用 ”菜品“ 這個(gè)詞匯(盡量不要這么做),但大家都明確 ”菜品“ 具有不同的含義。這個(gè)區(qū)域被叫做上下文。當(dāng)然上下文不只是由二義性決定的,還有可能是完全不相干的概念產(chǎn)生,例如訂單和座位實(shí)際概念上并沒(méi)有強(qiáng)烈的關(guān)聯(lián)關(guān)系,我們?cè)谡勛坏臅r(shí)候完全在談別的東西,所以座位也應(yīng)該是單獨(dú)的上下文。識(shí)別上下文的邊界是 DDD 中最難得一部分,同時(shí)上下文邊界是由業(yè)務(wù)變化動(dòng)態(tài)變化的,我們把識(shí)別出邊界的上下文叫做限界上下文(Bounded Context)。限界上下文是一個(gè)非常有用的工具,限界上下文可以幫助我們識(shí)別出業(yè)務(wù)的邊界,并做適當(dāng)?shù)牟鸱帧?/span>限界上下文的識(shí)別難以有一個(gè)明確的準(zhǔn)則,上下文的邊界非常模糊,需要有經(jīng)驗(yàn)的工程師并充分討論才能得到一個(gè)好的設(shè)計(jì)。同時(shí)需要注意,限界上下文的劃分沒(méi)有對(duì)錯(cuò),只有是否合適??缦藿缟舷挛闹g模型的關(guān)聯(lián)有本質(zhì)的不同,我們用虛線標(biāo)出,后面會(huì)聊到這種區(qū)別。(領(lǐng)域模型v4) 使用上下文之后,帶來(lái)另外一個(gè)收獲。模型之間本質(zhì)上沒(méi)有多對(duì)多關(guān)系,如果有,說(shuō)明存在一個(gè)隱含的成員關(guān)系,這個(gè)關(guān)系沒(méi)有被充分的分析出來(lái),對(duì)后期的開(kāi)發(fā)會(huì)造成非常大的困擾。
聚合根、實(shí)體、值對(duì)象 上面的模型,尤其是解決二義性這個(gè)問(wèn)題之后,已經(jīng)能在實(shí)際開(kāi)發(fā)中很好地使用了。不過(guò)還是會(huì)有一些問(wèn)題沒(méi)有解決,實(shí)際開(kāi)發(fā)中,每種模型的身份可能不太一樣,訂單項(xiàng)必須依賴訂單的存在而存在,如果能在領(lǐng)域模型圖中體現(xiàn)出來(lái)就更好了。舉個(gè)例子來(lái)說(shuō),當(dāng)我們刪除訂單時(shí)候,訂單項(xiàng)應(yīng)該一起刪除,訂單項(xiàng)的存在必須依賴于訂單的存在。這樣業(yè)務(wù)邏輯是一致的和完整的,游離的訂單項(xiàng)對(duì)我們來(lái)說(shuō)沒(méi)有意義,除非有特殊的業(yè)務(wù)需求存在。為了解決這個(gè)問(wèn)題,對(duì)待模型就不再是一視同仁了。我們將那相關(guān)性極強(qiáng)的領(lǐng)域模型放到一起考慮,數(shù)據(jù)的一致性必須解決,同時(shí)生命周期也需要保持同步,我們把這個(gè)集合叫做聚合。聚合中需要選擇一個(gè)代表負(fù)責(zé)和全局通信,類似于一個(gè)部門的接口人,這樣就能確保數(shù)據(jù)保持一致。我們把這個(gè)模型叫做聚合根。當(dāng)一個(gè)聚合業(yè)務(wù)足夠簡(jiǎn)單時(shí),聚合有可能只有一個(gè)模型組成,這個(gè)模型就是聚合根,常見(jiàn)的就是配置、日志相關(guān)的。相對(duì)于非聚合根的模型,我們叫做實(shí)體。(領(lǐng)域模型v5) 我們把這個(gè)圖完善一下,聚合之間也是用虛線鏈接,為聚合根標(biāo)上橙色。識(shí)別聚合根需要一些技巧。- 聚合根本質(zhì)上也是實(shí)體,同屬于領(lǐng)域模型,用于承載業(yè)務(wù)邏輯和系統(tǒng)狀態(tài)。
- 實(shí)體的生命周期依附于聚合根,聚合根刪除實(shí)體應(yīng)該也需要被刪除,保持系統(tǒng)一致性,避免游離的臟數(shù)據(jù)。
- 聚合根負(fù)責(zé)和其他聚合通信,因此聚合根往往具有一個(gè)全局唯一標(biāo)識(shí)。例如,訂單有訂單 ID 和訂單號(hào),訂單號(hào)為全局業(yè)務(wù)標(biāo)識(shí),訂單 ID 為聚合內(nèi)關(guān)聯(lián)使用。聚合外使用訂單號(hào)進(jìn)行關(guān)聯(lián)應(yīng)用。
還有一類特殊的模型,這類模型只負(fù)責(zé)承載多個(gè)值的用處。在我們飯店的例子中,如果需要對(duì)賬單支持多國(guó)貨幣,我們將純數(shù)字的 price 字段修為 Price 類型。public Clsss Price(){ private String unit;
private BigDecimal value; public Price(String unit,BigDecimal value){ this.unit = unit; this.value = value; } } 價(jià)格這個(gè)模型,沒(méi)有自己的生命周期,一旦被創(chuàng)建出來(lái)就無(wú)須修改,因?yàn)樾薷木透淖兞诉@個(gè)值本身。所以我們會(huì)給這類的對(duì)象一個(gè)構(gòu)造方法,然后去除掉所有的 setter 方法。我們把沒(méi)有自己生命周期的模型,僅用來(lái)呈現(xiàn)多個(gè)字段的值的模型和對(duì)象,稱作為值對(duì)象。值對(duì)象一開(kāi)始不是特別好理解,但是理解之后會(huì)讓系統(tǒng)設(shè)計(jì)非常清晰?!钡刂贰笆且粋€(gè)顯著的值對(duì)象。當(dāng)訂單發(fā)貨后,地址中的某一個(gè)屬性不應(yīng)該被單獨(dú)修改,因?yàn)楸恍薷闹筮@個(gè)”地址“就不再是剛剛那個(gè)”地址“,判斷地址是否相同我們會(huì)使用它的具體值:省、市、地、街道等。值對(duì)象是相對(duì)于實(shí)體而言的,對(duì)比如下(實(shí)體和值對(duì)象對(duì)比)另外值得一提的是,一個(gè)模型被作為值對(duì)象還是實(shí)體看待不是一成不變的,某些情況下需要作為實(shí)體設(shè)計(jì),但是在另外的條件下卻最好作為值對(duì)象設(shè)計(jì)。地址,在一個(gè)大型系統(tǒng)充滿了二義性。我們使用藍(lán)色區(qū)別實(shí)體和聚合根,更新后的模型圖如下:(領(lǐng)域模型v6) 雖然我們使用 E-R 的方式描述模型和模型之間的關(guān)系,但是這個(gè)E-R圖使用了顏色、虛線,已經(jīng)和傳統(tǒng)的 E-R 圖大不相同,把這種圖暫時(shí)叫做CE-R圖(Classified Entity Relationship)。DDD沒(méi)有規(guī)定如何畫(huà)圖,你可以使用其他任何畫(huà)圖的方法表達(dá)領(lǐng)域模型。
使用領(lǐng)域模型指導(dǎo)程序設(shè)計(jì) 在了解到 DDD 之前,到底該用一對(duì)多和多對(duì)多關(guān)系?RESTful API 設(shè)計(jì)時(shí)到底應(yīng)該選哪一個(gè)對(duì)象作為資源地址,評(píng)價(jià)應(yīng)該放到訂單路徑下還是單獨(dú)出來(lái)?訂單刪除相關(guān)有多少對(duì)象應(yīng)該納入事務(wù)管理?在沒(méi)有領(lǐng)域模型之前,這些大概率憑借經(jīng)驗(yàn)決定,當(dāng)我們把領(lǐng)域模型設(shè)計(jì)出來(lái)之后,領(lǐng)域模型可以幫助我們做出這些指導(dǎo)。領(lǐng)域模型不只是為編寫(xiě)業(yè)務(wù)邏輯代碼使用,這樣對(duì)領(lǐng)域模型來(lái)說(shuō)就太可惜了。下面是領(lǐng)域模型指導(dǎo)軟件開(kāi)發(fā)的一些方面,具體細(xì)節(jié)后面會(huì)再逐個(gè)討論。指導(dǎo)數(shù)據(jù)庫(kù)設(shè)計(jì)通過(guò) CE-R 圖,我們明顯可以設(shè)計(jì)出數(shù)據(jù)庫(kù)了。不過(guò)還有一些細(xì)節(jié)需要注意。首先,在之前的認(rèn)知里面,多對(duì)多關(guān)系是非常正常的。但是通過(guò)對(duì)領(lǐng)域模型的分析后發(fā)現(xiàn),傳統(tǒng)處理多對(duì)多關(guān)系時(shí),需要額外增加一張關(guān)聯(lián)表,這張關(guān)聯(lián)表本質(zhì)上是一個(gè)”關(guān)系“的實(shí)體沒(méi)有被發(fā)掘出來(lái)。否則,在實(shí)際開(kāi)發(fā)中會(huì)造成系統(tǒng)耦合,以及使用 ORM 的時(shí)候產(chǎn)生困惑。菜品和訂單之間是多對(duì)多關(guān)系嗎?如果是,菜品和訂單之間耦合了。實(shí)際上,菜品的管理處于系統(tǒng)操作的上游,菜品不依賴訂單的任何操作,也就是說(shuō)訂單的任何變化菜品無(wú)需關(guān)心。訂單擁有多個(gè)訂單項(xiàng),每個(gè)訂單項(xiàng)從菜品讀入數(shù)據(jù)并拷貝,或者引用一個(gè)菜品的全局 ID (菜品在另外一個(gè)聚合)。這樣在設(shè)計(jì)表結(jié)構(gòu)時(shí)訂單和訂單項(xiàng)關(guān)聯(lián),訂單項(xiàng)不關(guān)聯(lián)菜品。訂單項(xiàng)應(yīng)該從程序讀取菜品信息??雌饋?lái)多對(duì)多的關(guān)系,被細(xì)致分析后,變成了一個(gè)一對(duì)多關(guān)系。(數(shù)據(jù)庫(kù)設(shè)計(jì)) 在使用 ORM 時(shí),良好的領(lǐng)域模型尤其有用。不合適的關(guān)聯(lián)關(guān)系不僅讓 ORM 關(guān)聯(lián)變得混亂,還會(huì)讓 ORM 的性能變差。使用領(lǐng)域模型建立數(shù)據(jù)庫(kù)的要點(diǎn):留意多對(duì)多關(guān)系,并拆解成一對(duì)多關(guān)系 值對(duì)象和實(shí)體往往為一對(duì)一關(guān)系 使用 ORM 時(shí),聚合根和實(shí)體可以配置為級(jí)聯(lián)刪除和更新 - 禁止聚合根之間進(jìn)行關(guān)聯(lián)
指導(dǎo) API 設(shè)計(jì)RESTful API 已經(jīng)變成了主流 API 設(shè)計(jì)方式,當(dāng)設(shè)計(jì)好領(lǐng)域?qū)ο蠛螅O(shè)計(jì) API 的難度大大降低。使用聚合根作為 URI 的根路徑,使用實(shí)體作為子路徑。通過(guò) ID 作為 Path 參數(shù)。(API設(shè)計(jì)) 值對(duì)象沒(méi)有 ID,應(yīng)該只能依附于某個(gè)實(shí)體的路徑下做更新操作。(API設(shè)計(jì)v2) 另外根據(jù)這個(gè)關(guān)系,處理批量操作的時(shí)候應(yīng)該在實(shí)體的上一級(jí)完成,例如批量添加訂單的訂單項(xiàng),可以設(shè)計(jì)為:POST /orders/{orderId}/items-batch POST /orders/{orderId}/items/batch 指導(dǎo)對(duì)象設(shè)計(jì)在實(shí)踐中過(guò)程中,像 Java、Typescript具有類型系統(tǒng)的語(yǔ)言,對(duì)象很容易被誤用。如果 User 對(duì)象既被拿來(lái)當(dāng)做數(shù)據(jù)庫(kù)操作使用,又被拿來(lái)當(dāng)做接口呈現(xiàn)使用,這個(gè)類最終變成了上帝類,存在大量可有可無(wú)的屬性。例如用戶注冊(cè)時(shí)候需要輸入重復(fù)密碼,如果在 User 對(duì)象中添加 confirmPassword 屬性,存儲(chǔ)時(shí)候確并不需要。因此 DDD 中,數(shù)據(jù)庫(kù)各種對(duì)象的使用應(yīng)該針對(duì)不同的場(chǎng)景設(shè)計(jì)。回到我們上面說(shuō)的技術(shù)復(fù)雜度和業(yè)務(wù)復(fù)雜度中來(lái)。領(lǐng)域模型解決業(yè)務(wù)復(fù)雜度的問(wèn)題,領(lǐng)域模型只應(yīng)該被用作處理業(yè)務(wù)邏輯,存儲(chǔ)、業(yè)務(wù)表現(xiàn)都應(yīng)該和領(lǐng)域模型無(wú)關(guān)。(對(duì)象設(shè)計(jì)) 簡(jiǎn)單來(lái)說(shuō),可以把這些 Plain Object 分為三類:另外,在使用領(lǐng)域模型使用上也需要額外注意指導(dǎo)代碼組織代碼組織,通俗來(lái)說(shuō)就是如何分包。一種狹義的對(duì) DDD 的理解就是指按照 DDD 風(fēng)格進(jìn)行代碼組織,雖然 DDD 的內(nèi)容遠(yuǎn)不止于此。在很長(zhǎng)一段時(shí)間,我對(duì) DDD 分包策略陷入困惑,后來(lái)我明白到,討論 DDD 風(fēng)格的分包,必須將單體引用和微服務(wù)應(yīng)用分開(kāi)考慮。微服務(wù)應(yīng)用在邏輯上和解耦良好的單體應(yīng)用是一致的。但是微服務(wù)是一種分布式架構(gòu),映射到單體應(yīng)用中,各個(gè)包分布到不同的服務(wù)器中了。我們先以單體應(yīng)用入手,最后再討論如何將單體應(yīng)用架構(gòu)映射到到微服務(wù)中。在事務(wù)腳本的模式中,我們一般將代碼分為三層架構(gòu)。DDD 特別的抽離出一層叫做 application。這一層是 DDD 的精華,領(lǐng)域模型關(guān)心業(yè)務(wù)邏輯,但是不關(guān)心業(yè)務(wù)場(chǎng)景。application 用來(lái)隔離業(yè)務(wù)場(chǎng)景,顯得非常重要。舉個(gè)例子,用戶被添加到系統(tǒng)中,領(lǐng)域模型處理的是: 用戶被添加 授予基本權(quán)限 積分規(guī)則創(chuàng)建 賬戶創(chuàng)建(三戶模型,客戶、用戶、賬戶往往分開(kāi)) 但是,用戶被添加到系統(tǒng)中由多個(gè)應(yīng)用場(chǎng)景觸發(fā)。用戶被邀請(qǐng)注冊(cè) 用戶自己注冊(cè) application 需要隔離應(yīng)用場(chǎng)景,并組織調(diào)配領(lǐng)域服務(wù),才能使得領(lǐng)域服務(wù)真正被復(fù)用。因此 application 需要承擔(dān)事務(wù)管理、權(quán)限控制、數(shù)據(jù)校驗(yàn)和轉(zhuǎn)換等操作。當(dāng)領(lǐng)域服務(wù)被調(diào)用時(shí),應(yīng)該是純粹業(yè)務(wù)邏輯,并與場(chǎng)景無(wú)關(guān)。如果我們將三層架構(gòu)和 DDD 架構(gòu)對(duì)比,DDD 架構(gòu)如右圖所示。(三層架構(gòu)對(duì)比) 我們將 DDD 的代碼架構(gòu)展開(kāi),可以看到更為細(xì)節(jié)的內(nèi)容。DDD 代碼實(shí)現(xiàn)上需要 Repository、Factory 等概念,但這些是可選的,我們?cè)诤竺婢唧w講代碼結(jié)構(gòu)的部分再闡述。(單體DDD架構(gòu)) 我們?cè)賮?lái)看,DDD 的單體應(yīng)用架構(gòu)映射到微服務(wù)架構(gòu)下會(huì)是怎么樣的。(單體到微服務(wù)) 微服務(wù)必須考慮到不再是一個(gè)服務(wù),Domain 層被抽離出來(lái)作為 Domain Server 存在,Domain Server 不關(guān)心業(yè)務(wù)場(chǎng)景,因此不需要 application 層。Application Server 需要 Application 層,Domain 層由后端的 Domain Server 提供。另外補(bǔ)充,一些 DDD 代碼組織的基本邏輯:
|