如何使用 DDD 指導(dǎo)微服務(wù)拆分?
本文轉(zhuǎn)載自微信公眾號(hào)「架構(gòu)精進(jìn)之路」,作者架構(gòu)精進(jìn)之路。轉(zhuǎn)載本文請(qǐng)聯(lián)系架構(gòu)精進(jìn)之路公眾號(hào)。
軟件架構(gòu)發(fā)展經(jīng)歷
軟件架構(gòu)的發(fā)展經(jīng)歷了從單體架構(gòu)、垂直架構(gòu)、SOA架構(gòu)到微服務(wù)架構(gòu)以及到現(xiàn)在最新的service mesh(網(wǎng)格服務(wù)架構(gòu))的過(guò)程。借用dubbo的網(wǎng)站架構(gòu)發(fā)展圖和說(shuō)明:
微服務(wù)存在的問(wèn)題
進(jìn)入微服務(wù)之后 , 解決了集中式架構(gòu)的單體應(yīng)用很多問(wèn)題, 但是新的問(wèn)題應(yīng)運(yùn)而生 , 微服務(wù)的力度應(yīng)該多大 ?微服務(wù)如何設(shè)計(jì)呢?微服務(wù)如何拆分 ?微服務(wù)邊界在哪里 ?
很長(zhǎng)時(shí)間人們都沒(méi)有解決這一問(wèn)題,就連Martin Fowler在提出微服務(wù)架構(gòu)的時(shí)候也沒(méi)有告訴我們這該如何拆分微服務(wù)。
甚至在很長(zhǎng)的時(shí)間里人們對(duì)微服務(wù)拆分產(chǎn)生了一些誤解, 有人認(rèn)為:"微服務(wù)很簡(jiǎn)單,就是將之前的單體應(yīng)用拆分成多個(gè)部署包, 或者將原來(lái)的單體應(yīng)用架構(gòu)替換為一套支持微服務(wù)的技術(shù)架構(gòu),就算是微服務(wù)了。" 還有人認(rèn)為微服務(wù)應(yīng)該拆分得越小越好。
鑒于上述情形, 很多項(xiàng)目因?yàn)榍捌诓鸱诌^(guò)度, 導(dǎo)致復(fù)雜度過(guò)高, 導(dǎo)致后期難以運(yùn)維甚至難以上線。
可以得出一個(gè)結(jié)論:微服務(wù)拆分困境產(chǎn)生的根本原因就是不知道業(yè)務(wù)或者微服務(wù)的邊界到底在什么地方。換句話說(shuō),確定了業(yè)務(wù)邊界和應(yīng)用邊界,這個(gè)困境也就迎刃而解了。
DDD的誕生
而DDD就是解決了這個(gè)確定業(yè)務(wù)邊界的問(wèn)題,可見(jiàn)DDD并不是一種技術(shù)架構(gòu),而是一種劃分業(yè)務(wù)領(lǐng)域范圍的方法論。DDD的興起是由于很多熟悉領(lǐng)域驅(qū)動(dòng)建模(DDD)的工程師在進(jìn)行微服務(wù)設(shè)計(jì)時(shí), 發(fā)現(xiàn)用DDD的思路進(jìn)行業(yè)務(wù)梳理可以很好規(guī)劃服務(wù)邊界, 可以很好實(shí)現(xiàn)微服務(wù)內(nèi)部和外部的"高內(nèi)聚、低耦合"。于是越來(lái)越多的人將DDD作為業(yè)務(wù)劃分的指導(dǎo)思想。
DDD是一種拆解業(yè)務(wù)、劃分業(yè)務(wù)、確定業(yè)務(wù)邊界的方法, 是一種高度復(fù)雜的領(lǐng)域設(shè)計(jì)思想,將我們的問(wèn)題拆分成一個(gè)個(gè)地域, 試圖分離技術(shù)實(shí)現(xiàn)的復(fù)雜性,主要解決的是軟件難以理解難以演進(jìn)的問(wèn)題,DDD不是一種架構(gòu), 而是一種架構(gòu)方法論, 目的就是將復(fù)雜問(wèn)題領(lǐng)域簡(jiǎn)單化, 幫助我們?cè)O(shè)計(jì)出清晰的領(lǐng)域和邊界, 可以很好的實(shí)現(xiàn)技術(shù)架構(gòu)的演進(jìn)。DDD包括兩部分,戰(zhàn)略設(shè)計(jì)部分和戰(zhàn)術(shù)設(shè)計(jì)部分。
- 戰(zhàn)略設(shè)計(jì)主要從業(yè)務(wù)視角出發(fā),建立業(yè)務(wù)領(lǐng)域模型,劃分領(lǐng)域邊界,建立通用語(yǔ)言的限界上下文,限界上下文可以作為微服務(wù)設(shè)計(jì)的參考邊界。
- 戰(zhàn)術(shù)設(shè)計(jì)則從技術(shù)視角出發(fā),側(cè)重于領(lǐng)域模型的技術(shù)實(shí)現(xiàn),完成軟件開(kāi)發(fā)和落地,包括:聚合根、實(shí)體、值對(duì)象、領(lǐng)域服務(wù)、應(yīng)用服務(wù)和資源庫(kù)等代碼邏輯的設(shè)計(jì)和實(shí)現(xiàn)。
微服務(wù)拆分難題
開(kāi)發(fā)者在剛開(kāi)始嘗試實(shí)現(xiàn)自己的微服務(wù)架構(gòu)時(shí),往往會(huì)產(chǎn)生一系列問(wèn)題 :
- 微服務(wù)到底應(yīng)該怎么劃分?
- 一個(gè)典型的微服務(wù)到底應(yīng)該有多微?
- 如果做了微服務(wù)設(shè)計(jì),最后真的會(huì)有好處嗎?
回答上面的問(wèn)題需要首先了解微服務(wù)設(shè)計(jì)的邏輯,科學(xué)的架構(gòu)設(shè)計(jì)應(yīng)該通過(guò)一些輸入并逐步推導(dǎo)出結(jié)果,架構(gòu)師要避免憑空設(shè)計(jì)和“拍腦門(mén)”的做法。
服務(wù)的劃分有一些基本的方法和原則,通過(guò)這些方法能讓微服務(wù)劃分更有操作性。最終在微服務(wù)落地實(shí)施時(shí)也能按圖索驥,無(wú)論是對(duì)遺留系統(tǒng)改造還是全新系統(tǒng)的架構(gòu)都能游刃有余。
微服務(wù)拆分的幾個(gè)階段
在開(kāi)始劃分微服務(wù)之前,架構(gòu)師需要在大腦中有一個(gè)重要的認(rèn)識(shí):微服務(wù)只是手段,不是目的。
微服務(wù)架構(gòu)是為了讓系統(tǒng)變得更容易拓展、更富有彈性。在把單體應(yīng)用變成靠譜的微服務(wù)架構(gòu)之前,單體系統(tǒng)的各個(gè)模塊應(yīng)該是合理、清晰地。
也就是說(shuō),從邏輯上單體系統(tǒng)和微服務(wù)沒(méi)有區(qū)別,某種理想情況下微服務(wù)只是把單體系統(tǒng)的各個(gè)模塊分開(kāi)部署了而已
大量的實(shí)踐教訓(xùn)告訴我們,混沌的微服務(wù)架構(gòu),比解耦良好的單體應(yīng)用會(huì)帶來(lái)更多麻煩。
混亂的微服務(wù)VS良好的單體
領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)立足于面向?qū)ο笏枷?,從業(yè)務(wù)出發(fā),通過(guò)領(lǐng)域模型的方式反映系統(tǒng)的抽象,從而得到合理的服務(wù)劃分。
采用 DDD 來(lái)進(jìn)行業(yè)務(wù)建模和服務(wù)拆分時(shí),可以參考下面幾個(gè)階段:
- 使用 DDD(領(lǐng)域驅(qū)動(dòng)建模) 進(jìn)行業(yè)務(wù)建模,從業(yè)務(wù)中獲取抽象的模型(例如訂單、用戶),根據(jù)模型的關(guān)系進(jìn)行劃分限界上下文。
- 檢驗(yàn)?zāi)P褪欠竦玫胶线m的的抽象,并能反映系統(tǒng)設(shè)計(jì)和響應(yīng)業(yè)務(wù)變化。
- 從 DDD 的限界上下文往微服務(wù)轉(zhuǎn)化,并得到系統(tǒng)架構(gòu)、API列表、集成方式等產(chǎn)出。
使用DDD劃分微服務(wù)的過(guò)程
如何抽象?
抽象需要找到看似無(wú)關(guān)事務(wù)的內(nèi)在聯(lián)系,對(duì)微服務(wù)的設(shè)計(jì)尤為重要。
然而現(xiàn)實(shí)的例子比比皆是,電信或移動(dòng)營(yíng)業(yè)廳還需要用戶分兩步辦理號(hào)卡業(yè)務(wù)、寬帶業(yè)務(wù)。原始是不合適的抽象模型造成的,并最終影響了微服務(wù)的劃分。
我們可以使用概念圖來(lái)描述一些概念的抽象關(guān)系。
商品這一概念的概念圖
如果沒(méi)有抽象出領(lǐng)域模型,就得不到正確的微服務(wù)劃分。
使用DDD進(jìn)行業(yè)務(wù)建模
通過(guò)利用DDD對(duì)系統(tǒng)從業(yè)務(wù)的角度分析,對(duì)系統(tǒng)進(jìn)行抽象后,得到內(nèi)聚更高的業(yè)務(wù)模型集合,在DDD中一組概念接近、高度內(nèi)聚并能找到清晰的邊界的業(yè)務(wù)模型被稱作限界上下文(Bounded Context)。
限界上下文可以視為邏輯上的微服務(wù),或者單體應(yīng)用中的一個(gè)組件。
在電商領(lǐng)域就是訂單、商品以及支付等幾個(gè)在電商領(lǐng)域最為常見(jiàn)的概念;在社交領(lǐng)域就是用戶、群組、消息等。
DDD的方法論中是如何找到子系統(tǒng)的邊界的呢?
其中一項(xiàng)實(shí)踐叫做事件風(fēng)暴工作坊,工作坊要求業(yè)務(wù)需求提出者和技術(shù)實(shí)施者協(xié)作完成領(lǐng)域建模。把系統(tǒng)狀態(tài)做出改變的事件作為關(guān)鍵點(diǎn),從系統(tǒng)事件的角度觸發(fā),提取能反應(yīng)系統(tǒng)運(yùn)作的業(yè)務(wù)模型。再進(jìn)一步識(shí)別模型之間的關(guān)系,劃分出限界上下文,可以看做邏輯上的微服務(wù)。
事件是系統(tǒng)數(shù)據(jù)流中的關(guān)鍵點(diǎn),類似于電影制作中的關(guān)鍵幀。
例如系統(tǒng)管理員可以登錄、創(chuàng)建商品、上架商品,對(duì)應(yīng)的系統(tǒng)狀態(tài)的改變是用戶已登錄、商品已創(chuàng)建、商品已經(jīng)上架;相應(yīng)的顧客可以登錄、創(chuàng)建訂單、支付,對(duì)應(yīng)的系統(tǒng)狀態(tài)改變是用戶已登錄、訂單已創(chuàng)建、訂單已支付。
利用事件刺探業(yè)務(wù)黑盒并抽象出模型
在得到模型之后,通過(guò)分析模型之間的關(guān)系得出限界上下文。例如商品屬性和商品相對(duì)于用戶、用戶組關(guān)系更為密切,通過(guò)這些關(guān)系作出限界上下文拆分的基本線索。
其次是識(shí)別模型中的二義性,讓限界上下文劃分更為準(zhǔn)確。
例如,在電商領(lǐng)域,另外一個(gè)不恰當(dāng)設(shè)計(jì)的例子是:把訂單中的訂單項(xiàng)當(dāng)做和商品同樣的概念劃分到了商品服務(wù)
但訂單中的商品實(shí)際上和商品庫(kù)中的商品不是同一個(gè)概念。當(dāng)訂單需要修改訂單下的商品信息時(shí),需要訪問(wèn)商品服務(wù),這勢(shì)必造成了訂單和商品服務(wù)的耦合。
合理的設(shè)計(jì)應(yīng)該是:商品服務(wù)提供商品的信息給訂單服務(wù),但是訂單服務(wù)沒(méi)有理由修改商品信息,而是訪問(wèn)作為商品快照的訂單項(xiàng)。
訂單項(xiàng)應(yīng)該作為一個(gè)獨(dú)立的概念被劃分到訂單服務(wù)中,而不是和商品使用同一個(gè)概念,甚至共享同一張數(shù)據(jù)庫(kù)表。
典型具有”二義性“陷阱的場(chǎng)景
一組關(guān)系密切的模型形成了上下文(context),二義性的識(shí)別能幫我們找到上下文的邊界(bounded)。
驗(yàn)證和評(píng)審領(lǐng)域模型
前面我們說(shuō)到限界上下文可以作為邏輯上的微服務(wù),但并不意味著我們可以直接把限界上下文變成微服務(wù)。
限界上下文被設(shè)計(jì)出來(lái)后,驗(yàn)證它的方法可以從我們采用微服務(wù)的兩個(gè)目的出發(fā):降低耦合、容易擴(kuò)展,可以作為限界上下文評(píng)審原則:
原則1:設(shè)計(jì)出來(lái)的限界上下文之間的互相依賴應(yīng)該越少越好,依賴的上游不應(yīng)該知道下游的信息。
原則2:使用潛在業(yè)務(wù)進(jìn)行適配,如果能在一定程度上響應(yīng)業(yè)務(wù)變化,則證明用它指導(dǎo)出來(lái)的微服務(wù)可以在相當(dāng)一段時(shí)間內(nèi)足以支撐應(yīng)用開(kāi)發(fā)。
但是理想的領(lǐng)域模型往往抽象程度、成本、復(fù)用性這幾個(gè)因素中獲取平衡。
”抽象”的成本
用一個(gè)簡(jiǎn)單的圖來(lái)表達(dá)話,我們的領(lǐng)域模型設(shè)計(jì)往往在復(fù)用性和成本取得平衡的中間區(qū)域才有實(shí)用價(jià)值。
幾個(gè)典型的誤區(qū)
在大量使用DDD指導(dǎo)微服務(wù)拆分的實(shí)踐后,我們發(fā)現(xiàn)很多系統(tǒng)設(shè)計(jì)存在一些常見(jiàn)的誤區(qū)
主要分為兩類:未成功做出抽象、抽象程度過(guò)高、錯(cuò)誤的抽象。
1)未成功做出抽象
在實(shí)際開(kāi)發(fā)過(guò)程中,大家都有一個(gè)體會(huì),設(shè)計(jì)階段只考慮了一些常見(jiàn)的服務(wù),但是發(fā)現(xiàn)項(xiàng)目中有大量可以重用的邏輯,并應(yīng)該做成單獨(dú)服務(wù)。
當(dāng)我們?cè)谧龇?wù)拆分時(shí),遺漏了服務(wù)的結(jié)果是有一些業(yè)務(wù)邏輯被分散到各個(gè)服務(wù)中,并不斷重復(fù)。
2)抽象程度過(guò)高
抽象程度過(guò)高最典型的一個(gè)特征是得到的限界上下文極端的微小。
抽象程度過(guò)高帶來(lái)的成本有:更多的微服務(wù)部署帶來(lái)的運(yùn)維壓力、開(kāi)發(fā)調(diào)試難度提高、服務(wù)間通信帶來(lái)的性能開(kāi)銷、跨服務(wù)的分布式事務(wù)協(xié)調(diào)等。因此抽象不是越高越好,應(yīng)根據(jù)實(shí)際業(yè)務(wù)需要和成本考慮。
那相應(yīng)的,微服務(wù)到底應(yīng)該多小呢?
業(yè)界流傳一句話來(lái)形容,微服務(wù)應(yīng)該多?。?ldquo;一個(gè)微服務(wù)應(yīng)該可以在二周內(nèi)完成重寫(xiě)“。
這句話可能只是一句調(diào)侃,如果真的作為微服務(wù)應(yīng)該多微的標(biāo)準(zhǔn)是不可取的。
微服務(wù)的大小應(yīng)該取決于劃分限界上下文時(shí)各個(gè)限界上下文內(nèi)聚程度。
3)錯(cuò)誤抽象
對(duì)微服務(wù)或DDD理解不夠。模型具有二義性,被放到不同的限界上下文。
例如,訂單中的收貨地址、用戶配置的常用地址以及地址庫(kù)中的標(biāo)準(zhǔn)地址。
這三種地址雖然名稱類似,但是在概念上完全不是一回事
假如架構(gòu)師將”地址“劃分到了標(biāo)準(zhǔn)地址庫(kù)中,勢(shì)必會(huì)造成用戶上下文和系統(tǒng)配置上下文、訂單上下文存在不必要的耦合。
抽象錯(cuò)誤帶來(lái)的依賴
上圖的右邊為正常的依賴關(guān)系,左邊產(chǎn)生了不正常的依賴,會(huì)進(jìn)一步產(chǎn)生雙向依賴。
從限界上下文到系統(tǒng)架構(gòu)
在通過(guò) DDD 得到領(lǐng)域模型和限界上下文后,理論上我們已經(jīng)得到了微服務(wù)的拆分。但是,限界上下文到系統(tǒng)架構(gòu)還需要完成下面幾件事。
1)設(shè)計(jì)微服務(wù)之間的依賴關(guān)系
一個(gè)合理的分布式系統(tǒng),系統(tǒng)之間的依賴應(yīng)該是非常清晰地。依賴,在軟件開(kāi)發(fā)中指的是一個(gè)應(yīng)用或者組件需要另外一個(gè)組件提供必要的功能才能正常工作。因此被依賴的組件是不知道依賴它的應(yīng)用的,換句話說(shuō),被調(diào)用者不需要知道調(diào)用方的信息,否則這不是一個(gè)合理的依賴
在微服務(wù)設(shè)計(jì)時(shí),如果 domain service 需要通過(guò)一個(gè) from 參數(shù),根據(jù)不同的渠道做出不同的行為,這對(duì)系統(tǒng)的拓展是致命的。例如,用戶服務(wù)對(duì)于訪問(wèn)他的來(lái)源不應(yīng)該知曉;用戶服務(wù)應(yīng)該對(duì)訂單、商品、物流等訪問(wèn)者提供無(wú)差別的服務(wù)。
因此,微服務(wù)的依賴關(guān)系可以總結(jié)為:上游系統(tǒng)不需要知道下游系統(tǒng)信息,否則請(qǐng)重新審視系統(tǒng)架構(gòu)。
2)設(shè)計(jì)微服務(wù)間集成方式
拆分微服務(wù)是為了更好的集成到一起,對(duì)于后續(xù)落地來(lái)說(shuō),還有服務(wù)集成這一重要的階段。
微服務(wù)之間的集成方式會(huì)受到很多因素的制約,前面在討論微服務(wù)到底有多微的時(shí)候就順便提到了集成會(huì)帶來(lái)成本,處于不同的目的可以采用不同的集成方式。
- 采用 RPC(遠(yuǎn)程調(diào)用) 的方式集成。
使用RPC的方式可以讓開(kāi)發(fā)者非常容易的切換到分布式系統(tǒng)開(kāi)發(fā)中來(lái),但是RPC的耦合性依然很高,同時(shí)需要對(duì)RPC平臺(tái)依賴。業(yè)界優(yōu)秀的RPC框架有dubbo、Grpc、thrift等
- 采用消息的方式集成。
使用消息的方式異步傳輸數(shù)據(jù),服務(wù)之間使用發(fā)布-訂閱的方式交互。另外一種思想是通過(guò)對(duì)系統(tǒng)事件傳遞,因此產(chǎn)生了 Event Sourcing 這種集成模式,讓微服務(wù)具備天然的彈性。
- 采用RESTful方式集成。
RESTful是一種最大化利用HTTP協(xié)議的API設(shè)計(jì)方式,服務(wù)之間通過(guò)HTTP API集成。這種方式讓耦合變得極低,甚至稍作修改就可以暴露給外部系統(tǒng)使用。
這三種集成方式耦合程度由高到低,適用于不同的場(chǎng)景,需要根據(jù)實(shí)際情況選擇,甚至在系統(tǒng)中可能同時(shí)存在。
服務(wù)間集成的方式還有其他方式,一般來(lái)說(shuō),上面三種微服務(wù)集成的方式可以概括目前常見(jiàn)系統(tǒng)大部分需求。
總結(jié)
這篇文章主要研討了DDD火起來(lái)的原因, 解決了什么業(yè)界難題, 知道DDD主要思路 , 以及DDD大概的實(shí)現(xiàn)步驟等 。
邏輯往往比經(jīng)驗(yàn)更為重要。寫(xiě)這篇文章的初衷是為了得到微服務(wù)劃分的依據(jù)是什么,我該怎么有說(shuō)服力的回復(fù)?
是具體情況具體分析?By experience?還是說(shuō),我是通過(guò)一套方法對(duì)業(yè)務(wù)邏輯進(jìn)行分析得到的。
當(dāng)沒(méi)有足夠的經(jīng)驗(yàn)直接解決問(wèn)題,或問(wèn)題龐大到不足以使用經(jīng)驗(yàn)解決時(shí),能支撐你做出決策就只有對(duì)輸入問(wèn)題進(jìn)行有效的分析。
使用 DDD 指導(dǎo)微服務(wù)劃分,能在一定程度上彌補(bǔ)經(jīng)驗(yàn)的不足,做出有理有據(jù)的系統(tǒng)架構(gòu)設(shè)計(jì)。