在開(kāi)發(fā)一個(gè)微服務(wù)之前,我們要設(shè)計(jì)微服務(wù)。設(shè)計(jì)微服務(wù)和領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)(DDD)有密切的關(guān)系,DDD有助于我們?cè)O(shè)計(jì)微服務(wù),所以我們這一節(jié)主要講下領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的基本概念、建模方法、架構(gòu)等,以對(duì)我們?cè)O(shè)計(jì)微服務(wù)提供指導(dǎo)。
領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)DDD是目前比較火的一個(gè)軟件架構(gòu)術(shù)語(yǔ),在我看來(lái),這個(gè)其實(shí)是業(yè)務(wù)驅(qū)動(dòng)IT的一個(gè)具體體現(xiàn)。DDD的核心思想是在做IT設(shè)計(jì)之前,要對(duì)業(yè)務(wù)有深入的理解。DDD告訴了我們領(lǐng)域建模的方法并理論化。
1. DDD的基本概念
1)領(lǐng)域:領(lǐng)域就是一個(gè)組織所做的事情以及其中包含的一切,每個(gè)組織都有它的做事方式和業(yè)務(wù)范圍,這個(gè)業(yè)務(wù)范圍及在其中所進(jìn)行的活動(dòng)便是領(lǐng)域。對(duì)領(lǐng)域最了解的是業(yè)務(wù)專(zhuān)家,而不是IT人員。
2)模型:對(duì)領(lǐng)域的抽象和提煉,是對(duì)領(lǐng)域問(wèn)題的思考過(guò)程的總結(jié),由業(yè)務(wù)分析人員完成。模型承上啟下:向上,業(yè)務(wù)分析人員需要用模型和業(yè)務(wù)專(zhuān)家溝通;向下,業(yè)務(wù)分析人員用模型指導(dǎo)軟件設(shè)計(jì)師進(jìn)行設(shè)計(jì)并開(kāi)發(fā)軟件。模型的首要要求是一致性,同一模型必須是一致的、保持不變的。
3)上下文:可以認(rèn)為是模型的勢(shì)力地盤(pán),我的地盤(pán)我做主,大家要各司其職,不要越界。使用一個(gè)模型之前要進(jìn)行哪些操作,使用一個(gè)模型之后要進(jìn)行哪些善后。每個(gè)模型都有缺省的上下文,當(dāng)業(yè)務(wù)比較復(fù)雜,特別是要和存續(xù)系統(tǒng)對(duì)接時(shí),模型的上下文邊界就很重要。
2. 領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的戰(zhàn)略建模(原則)
也可以說(shuō)是原則,但我更愿意稱(chēng)之為戰(zhàn)略建模,就是在項(xiàng)目開(kāi)始要從宏觀的層面劃分清楚業(yè)務(wù)領(lǐng)域,各業(yè)務(wù)領(lǐng)域能各自為政(限界上下文),獨(dú)立進(jìn)化,對(duì)外又能形成一個(gè)統(tǒng)一的有機(jī)體(上下文映射)
1)限界上下文:
把模型的上下文限定在一定范圍,模型負(fù)責(zé)哪些事情,不負(fù)責(zé)哪些事情有清晰的定義。界定模型的上下文的原因是保持模型的一致性,不會(huì)受外界因素的干擾。在《領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)》一書(shū)里,將限界上下文比喻成細(xì)胞膜,很形象:”細(xì)胞膜不僅僅能把細(xì)胞內(nèi)部和外部區(qū)分開(kāi),而且還能決定通過(guò)的物質(zhì)“。
一般根據(jù)下面的因素界定模型的上下文:團(tuán)隊(duì)的組織結(jié)構(gòu)、現(xiàn)有的代碼庫(kù)模塊的劃分、數(shù)據(jù)庫(kù)的Schema等。推薦的做法是為每個(gè)領(lǐng)域創(chuàng)建一個(gè)獨(dú)立的模型,該模型的上下文是該領(lǐng)域或該領(lǐng)域的子集。
2) 上下文映射:
描述不同的上下文之間的交互關(guān)系。業(yè)務(wù)領(lǐng)域之間不是老死不相往來(lái),相反,業(yè)務(wù)領(lǐng)域之間存在各種各樣交互。交互有下面的類(lèi)別:
? ? ? a)共享內(nèi)核:兩個(gè)或多個(gè)限界上下文共享同一模型子集及模型相關(guān)聯(lián)的邏輯、代碼。團(tuán)隊(duì)之間需要統(tǒng)一共享,并且對(duì)共享內(nèi)核的修改要征得另外團(tuán)隊(duì)的同意。
? ? ? b)客戶(hù)-供應(yīng)商關(guān)系:兩個(gè)限界上下文是上下游的關(guān)系。處于下游的限界上下文(客戶(hù))嚴(yán)重依賴(lài)于處于上游的限界上下文(供應(yīng)商)的輸出,比如在電商領(lǐng)域,報(bào)表限界上下文嚴(yán)重依賴(lài)于在線(xiàn)購(gòu)物限界上下文的輸出(輸出會(huì)保存到數(shù)據(jù)庫(kù)里“客戶(hù)”和“供應(yīng)商”應(yīng)定時(shí)安排會(huì)議,”客戶(hù)“提交自己的需求,”供應(yīng)商“對(duì)”客戶(hù)“的需求進(jìn)行排期開(kāi)發(fā)?!肮?yīng)商”會(huì)開(kāi)發(fā)一些自己不需要但是“客戶(hù)“需要的功能和接口。接口是聯(lián)系“客戶(hù)”與“供應(yīng)商”的紐帶,需要好好維護(hù)。
? ? ? c)順從者:在客戶(hù)-供應(yīng)商關(guān)系里,當(dāng)供應(yīng)商沒(méi)有意愿滿(mǎn)足客戶(hù)的需求時(shí),客戶(hù)只能被動(dòng)接受供應(yīng)商提供的模型和接口。供應(yīng)商畢竟有自己要處理的事情,不可能靠“利他“理念推動(dòng)供應(yīng)商一直滿(mǎn)足客戶(hù)的需求?!崩骸笆侨说奶煨浴?duì)于“客戶(hù)”,可以走的一條路是自力更生,創(chuàng)建自己的模型滿(mǎn)足自己的需求,并逐步減少對(duì)“供應(yīng)商”的依賴(lài)。一旦”客戶(hù)“有了自己的模型,需要防崩潰層進(jìn)行對(duì)”供應(yīng)商“提供的接口進(jìn)行翻譯轉(zhuǎn)換。
? ? ? d)防崩潰層/反腐層/緩沖層:當(dāng)客戶(hù)-供應(yīng)商關(guān)系變?yōu)椤表槒恼摺瓣P(guān)系,或者需要和存續(xù)系統(tǒng)進(jìn)行交互時(shí),需要防崩潰層對(duì)上游提供的信息和數(shù)據(jù)做出翻譯,類(lèi)似于《設(shè)計(jì)模式》里的Adapter。“客戶(hù)”的防崩潰層通過(guò)調(diào)用“供應(yīng)商”提供的服務(wù)來(lái)獲取所需的數(shù)據(jù)或信息,經(jīng)過(guò)防崩潰層的翻譯、轉(zhuǎn)換、適配,轉(zhuǎn)變成和“客戶(hù)”端內(nèi)模型一致的數(shù)據(jù)。
? ? ?e)隔離通道:當(dāng)兩個(gè)系統(tǒng)之間沒(méi)有或者很少交集,或者沒(méi)有上下游關(guān)系時(shí),或者系統(tǒng)之間集成的難度很大而價(jià)值很小時(shí),就可以考慮隔離通道。每個(gè)系統(tǒng)都可以用如一個(gè)大型企業(yè)里的人力資源領(lǐng)域、供應(yīng)鏈領(lǐng)域、社區(qū)領(lǐng)域。當(dāng)我們拿到一個(gè)新的需求時(shí),我們應(yīng)該思考可否把它劃分為兩個(gè)或多個(gè)沒(méi)有相通之處的部分。如果可以,我們可以創(chuàng)建獨(dú)立的限界上下文,并獨(dú)立建模。
? ? ?f)開(kāi)發(fā)主機(jī)服務(wù):當(dāng)有上下游關(guān)系,不論是在客戶(hù)-供應(yīng)商或者順從者關(guān)系下,作為供應(yīng)商需要將和外面限界上下文交互的實(shí)體、邏輯包裝成服務(wù)開(kāi)發(fā)出去。應(yīng)該定義一種協(xié)議,并將該協(xié)議開(kāi)放出去,這樣其它上下文都可以通過(guò)該協(xié)議訪(fǎng)問(wèn)。值得注意的是,當(dāng)有特殊的需求,需要客戶(hù)端自己采用防崩潰層進(jìn)行翻譯,而不是擴(kuò)展協(xié)議,以保證協(xié)議的簡(jiǎn)單和連貫性。
? ? ?g)提煉:抓住問(wèn)題的本質(zhì)(主要矛盾),提煉業(yè)務(wù)的核心領(lǐng)域,圍繞核心領(lǐng)域投入重兵,其它的為支撐領(lǐng)域?yàn)楹诵念I(lǐng)域提供支撐。業(yè)務(wù)分析人員、軟件設(shè)計(jì)人員應(yīng)對(duì)核心領(lǐng)域的細(xì)節(jié)多加關(guān)注,這些細(xì)節(jié)是系統(tǒng)成功的關(guān)鍵。
3. DDD的戰(zhàn)術(shù)建模
戰(zhàn)術(shù)建模是指將不同的領(lǐng)域?qū)ο筮M(jìn)行分類(lèi),并為之建立模型
1)實(shí)體:擁有唯一的標(biāo)識(shí)符;標(biāo)識(shí)符在生命周期里不會(huì)發(fā)生變化;其它屬性在生命周期內(nèi)可以發(fā)生變化;需要被跟蹤
實(shí)體是DDD里一個(gè)很重要的概念,在DDD的開(kāi)始就要定義實(shí)體,圍繞實(shí)體開(kāi)展工作。
實(shí)體可以采用普通舊式Java對(duì)象(PO JO,Plain Old Java Object)描述。
2)值對(duì)象:Value Object,在該對(duì)象的生命周期內(nèi),屬性不會(huì)有任何改變的對(duì)象;值對(duì)象是不可變的,是可以共享的。值對(duì)象應(yīng)該很小,也可以很簡(jiǎn)單。
從實(shí)現(xiàn)上來(lái)講,一般是值對(duì)象設(shè)置為類(lèi)的私有屬性,通過(guò)構(gòu)造函數(shù)傳入值對(duì)象具體的值,當(dāng)需要一個(gè)不同的值時(shí),重新在構(gòu)造函數(shù)里傳入不同的值創(chuàng)建新的對(duì)象。
3)服務(wù)及服務(wù)對(duì)象:在領(lǐng)域建模的過(guò)程中,我們會(huì)發(fā)現(xiàn),有些活動(dòng)不屬于任一個(gè)實(shí)體或值對(duì)象的職責(zé),反而實(shí)體或值對(duì)象是這些活動(dòng)操作的對(duì)象,這些活動(dòng)對(duì)整個(gè)領(lǐng)域來(lái)說(shuō)有很重要,把這些活動(dòng)安排到任何一個(gè)被操作的對(duì)象里,都會(huì)破環(huán)該實(shí)體或值對(duì)象。從面向?qū)ο缶幊痰囊暯牵覀儠?huì)把這些活動(dòng)歸類(lèi)到一些對(duì)象里,這些對(duì)象叫服務(wù)對(duì)象,這些活動(dòng)叫服務(wù)。
服務(wù)對(duì)象不具備內(nèi)部的狀態(tài),它的唯一目的是提供操作領(lǐng)域內(nèi)實(shí)體或值對(duì)象的活動(dòng)。實(shí)體或值對(duì)象屬于服務(wù)的被操作對(duì)象,從語(yǔ)意上講,實(shí)體或值對(duì)象屬于賓語(yǔ),而不是主語(yǔ)。例如:生成報(bào)告,這個(gè)活動(dòng)里,報(bào)告屬于賓語(yǔ),屬于被操作的對(duì)象,因而生成報(bào)告不屬于報(bào)告的職責(zé),要單獨(dú)創(chuàng)建一個(gè)服務(wù)對(duì)象來(lái)執(zhí)行這一活動(dòng)。
服務(wù)也屬于領(lǐng)域模型的一部分,大部分的服務(wù)都會(huì)在DDD架構(gòu)模型里的領(lǐng)域?qū)印?/p>
4) 模塊:通過(guò)高內(nèi)聚、低耦合的原則將領(lǐng)域劃分為不同的模塊
實(shí)際操作中,一般把操作相同數(shù)據(jù)的或完成同一業(yè)務(wù)功能的部件劃分到一個(gè)模塊
模塊的劃分對(duì)于設(shè)計(jì)來(lái)說(shuō)粒度還是太粗,所以需要將模塊繼續(xù)細(xì)分到聚合和聚合根
5) 聚合和聚合根:聚合用來(lái)定義對(duì)象的所有權(quán)和邊界。
聚合:針對(duì)數(shù)據(jù)變化可以考慮成一個(gè)單元的一組關(guān)聯(lián)的實(shí)體和值對(duì)象。比如,要?jiǎng)h除時(shí),會(huì)一起被刪除;要?jiǎng)?chuàng)建時(shí),會(huì)一起被創(chuàng)建。
聚合使用聚合根將內(nèi)部和外部的對(duì)象劃分開(kāi)來(lái),聚合根是一個(gè)實(shí)體,是外部可以訪(fǎng)問(wèn)的唯一對(duì)象。
劃分聚合時(shí)要考慮的原則:
? ? ?a)數(shù)據(jù)的一致性:針對(duì)數(shù)據(jù)變化可以考慮成一個(gè)單元。執(zhí)行上,聚合外部的對(duì)象只持有對(duì)根的引用,而不能直接對(duì)聚合內(nèi)的實(shí)體進(jìn)行訪(fǎng)問(wèn)和更改。對(duì)聚合內(nèi)部的實(shí)體和對(duì)象的訪(fǎng)問(wèn)必須通過(guò)根對(duì)象。在一個(gè)聚合內(nèi)部,根可以修改其它的實(shí)體,但這是可控的。如果根從內(nèi)存中被刪除,聚合內(nèi)的其他對(duì)象也將被刪除。如果一個(gè)聚合中的對(duì)象被保存在數(shù)據(jù)庫(kù)里,通過(guò)查詢(xún)來(lái)獲得的只有根對(duì)象,其他的對(duì)象只能通過(guò)從根對(duì)象出發(fā)的導(dǎo)航關(guān)聯(lián)關(guān)系獲取
? ? ?b)聚合應(yīng)盡量小,盡量簡(jiǎn)單和容易理解,而不是盡量完整。所以,對(duì)于聚合,一個(gè)工作思路就是對(duì)聚合內(nèi)實(shí)體的關(guān)系進(jìn)行簡(jiǎn)化,盡量把關(guān)系對(duì)應(yīng)成1對(duì)1的關(guān)系,比如通過(guò)刪除非基本的關(guān)聯(lián)關(guān)系、增加約束等方式

6) 工廠(chǎng):創(chuàng)建對(duì)象的方法,將聚合作為一個(gè)整體來(lái)進(jìn)行創(chuàng)建??梢詤⒁?jiàn)《設(shè)計(jì)模式》里的工廠(chǎng)模式或者抽象工廠(chǎng)模式。
工廠(chǎng)也是領(lǐng)域設(shè)計(jì)中的一類(lèi)對(duì)象,工廠(chǎng)提供一個(gè)接口封裝復(fù)雜的封裝過(guò)程。
當(dāng)聚合里的實(shí)體組裝很簡(jiǎn)單,或者一個(gè)對(duì)象的創(chuàng)建不會(huì)涉及到其它對(duì)象的創(chuàng)建,可以采用根實(shí)體的構(gòu)造函數(shù)創(chuàng)建。
7) 資源庫(kù):也是模型設(shè)計(jì)里的一個(gè)對(duì)象,用來(lái)保存可用的聚合,并返回聚合根的引用。注意資源庫(kù)與存儲(chǔ)區(qū)的區(qū)別。資源庫(kù)屬于領(lǐng)域區(qū),封裝了對(duì)數(shù)據(jù)操作的細(xì)節(jié),對(duì)外部提供一個(gè)接口,隱藏了對(duì)數(shù)據(jù)操作的細(xì)節(jié)。而存儲(chǔ)區(qū)指數(shù)據(jù)庫(kù)、文件等持久化存儲(chǔ)設(shè)備,處于基礎(chǔ)架構(gòu)區(qū)。

工廠(chǎng)和資源庫(kù)、存儲(chǔ)區(qū)的關(guān)系:

4. DDD的多層架構(gòu)
同其它架構(gòu)方法類(lèi)似,DDD也是多層架構(gòu)。DDD的多層架構(gòu)分為了:1)UI層;2)應(yīng)用層;3)領(lǐng)域?qū)樱?)基礎(chǔ)架構(gòu)層。理解這幾個(gè)層級(jí),對(duì)于下面開(kāi)展微服務(wù)的工作很重要,所以要搞清楚這幾個(gè)層級(jí)的定位。
1)UI層,顧名思義,就是用戶(hù)界面層,用以提供和和用戶(hù)交互的界面。
2)應(yīng)用層:這個(gè)比較容易和其它架構(gòu)里提到的應(yīng)用混淆。在DDD里的應(yīng)用層,主要負(fù)責(zé)工作流程的編排工作。用服務(wù)的語(yǔ)言來(lái)講,應(yīng)用層可以稱(chēng)為是服務(wù)編排(傳統(tǒng)的SOA)層或者服務(wù)調(diào)度層(微服務(wù))。
3)領(lǐng)域?qū)邮穷I(lǐng)域驅(qū)動(dòng)設(shè)計(jì)中的一個(gè)很重要的層級(jí),在這幾個(gè)層級(jí)中,只有領(lǐng)域?qū)迂?fù)責(zé)領(lǐng)域模型。領(lǐng)域?qū)影▽?shí)體和業(yè)務(wù)邏輯。領(lǐng)域?qū)訉?shí)現(xiàn)了服務(wù)(實(shí)現(xiàn)業(yè)務(wù)邏輯)、資料庫(kù)(對(duì)數(shù)據(jù)實(shí)體進(jìn)行CRUD操作)等
4)基礎(chǔ)架構(gòu)層:這個(gè)也和其它架構(gòu)里提到的基礎(chǔ)架構(gòu)有區(qū)別。這里的基礎(chǔ)設(shè)施不僅僅包含計(jì)算、網(wǎng)絡(luò)、存儲(chǔ)等硬件設(shè)施,還包括應(yīng)用的基礎(chǔ)架構(gòu),如操作系統(tǒng)、數(shù)據(jù)庫(kù)、消息隊(duì)列等。基礎(chǔ)架構(gòu)為上層邏輯的實(shí)現(xiàn)提供支撐,并存儲(chǔ)數(shù)據(jù)

對(duì)DDD的回顧就到這里,下一節(jié)我們會(huì)基于DDD的思想設(shè)計(jì)一個(gè)網(wǎng)上叫車(chē)的微服務(wù),開(kāi)發(fā)并注冊(cè)到第一節(jié)開(kāi)發(fā)的Eureka上,敬請(qǐng)期待