通過領(lǐng)域驅(qū)動建模來劃分微服務(wù),真正的難點究竟在哪里?
本文轉(zhuǎn)載自微信公眾號「人月聊IT」,作者何明璐 。轉(zhuǎn)載本文請聯(lián)系人月聊IT公眾號。
今天這篇文章主要還是進一步對上篇文章里面沒有說清楚的地方進一步描述。
微服務(wù)劃分
注意微服務(wù)劃分涉及到兩個關(guān)鍵部分內(nèi)容。
- 哪些業(yè)務(wù)功能應(yīng)該聚合在一起。
- 哪些數(shù)據(jù)應(yīng)該聚合在一起,Owner是該微服務(wù)。
為何我不斷在強調(diào)這點。
因為在微服務(wù)之間的傳統(tǒng)單體應(yīng)用階段,也有架構(gòu)設(shè)計,也有組件劃分。但是一般來說只是應(yīng)用層業(yè)務(wù)功能如何聚合,而不會涉及到數(shù)據(jù)庫本身也要拆分的問題。
而微服務(wù)下的劃分必須是數(shù)據(jù)和功能全部拆分開。
微服務(wù)A只能對自己Owner的數(shù)據(jù)庫進行類似JDBC等直接連接操作,而對于其它微服務(wù)的數(shù)據(jù)庫只能夠通過應(yīng)用層暴露的API接口服務(wù)進行。
如上圖中紅叉的地方是不允許的。
傳統(tǒng)單體應(yīng)用架構(gòu)設(shè)計,雖然也進行了組件劃分,但是一般數(shù)據(jù)庫沒有拆分。組件雖然也可以單獨找APP Server獨立部署,但是底層數(shù)據(jù)庫還是耦合在一起的。
因此單個組件并沒有實現(xiàn)從數(shù)據(jù)庫到應(yīng)用層的完全解耦。
雖然組件設(shè)計開發(fā)規(guī)范要求組件A需要通過API訪問組件B提供的能力,但是實際大部分時候組件A是直接在訪問后臺數(shù)據(jù)庫表。
因為這樣做開發(fā)實現(xiàn)起來最簡單。
所有很多規(guī)范約束實際很難真正落地,組件化后仍然是緊耦合狀態(tài)。
因此微服務(wù)劃分同時需要考慮功能和數(shù)據(jù)兩個層面的聚合和拆分,這個劃分實際比傳統(tǒng)架構(gòu)設(shè)計中的組件劃分更難,考慮的點也需要更多。
領(lǐng)域模型來劃分微服務(wù)
領(lǐng)域驅(qū)動設(shè)計概念來源于2004年著名建模專家Eric Evans,也出了一本經(jīng)典的領(lǐng)域驅(qū)動設(shè)計的建模書籍。但是DDD本身多年前發(fā)展并不是很火。
如果回到10多年前,主流的架構(gòu)設(shè)計思路偏RUP+UML,而DDD建模思路實際更加復(fù)雜,能夠應(yīng)用好本身不容易。其次,很多IT項目本身也不需要用到這么復(fù)雜的建模方法。
而最近5年,隨著微服務(wù)架構(gòu)的流行,DDD領(lǐng)域建模再次受到重視。
一個核心應(yīng)用點就在于微服務(wù)究竟應(yīng)該如何劃分?
領(lǐng)域驅(qū)動設(shè)計中的限界上下文,子域劃分給了很好的思路和方法。比如常用的基于事件風暴來劃分上下文。
識別事件和命令
事件你可以理解為一個事物所編寫出來的最終狀態(tài),例如訂單已創(chuàng)建,支付已完成,商品已發(fā)貨等即是關(guān)鍵的事件。而命令可以理解為具體的業(yè)務(wù)功能或操作,比如創(chuàng)建訂單,檢索商品,扣減庫存等。
可以看到事件和命令的識別和分析中,都可以識別到具體的領(lǐng)域?qū)ο蟆?/p>
基于領(lǐng)域的對象進行聚合
如何進行聚合?簡單來說仍然是通過識別和梳理出來的共性領(lǐng)域?qū)ο筮M行聚合。將對同一領(lǐng)域?qū)ο蟮乃忻詈褪录季酆显谝黄稹?/p>
進行上下文邊界的劃分
基于聚合完成的情況進行上下文邊界的劃分,這里實際上不同的領(lǐng)域?qū)ο笕绻麑儆谕粋€大類的業(yè)務(wù)場景,仍然是可以劃分到一個上下文里面的。
也就是不是簡單地按聚合完成的領(lǐng)域?qū)ο髣澐稚舷挛倪吔纾芏嗪秃诵念I(lǐng)域?qū)ο笙嚓P(guān)的附屬對象也需要劃分到同一個上下文的。
比如電商里面的訂單是一個大量的領(lǐng)域?qū)ο?,可以劃分為獨立的上下文,但是對?yīng)的購物車也是我們識別的對象,購物車本身同樣屬于產(chǎn)品訂購場景,訂單的擴展附屬對象,因此需要將購物車也劃分到訂單上下文里面。
再次總結(jié)下整個方法思路如下。
即仍然是業(yè)務(wù)場景驅(qū)動分析加頭腦風暴,識別出關(guān)鍵的業(yè)務(wù)事件(如果采用Scrum方法,這個也可以理解為獨立的UserStory)。任何一個業(yè)務(wù)事件都包括了業(yè)務(wù)動作,業(yè)務(wù)對象和對象狀態(tài)三要素。
注意,常用的事件風暴法里面更加強調(diào)先找到狹義的事件,即對象狀態(tài)。比如已發(fā)貨,已付款,已凍結(jié)等狀態(tài)事件,然后再去分析狀態(tài)事件對應(yīng)的對象和業(yè)務(wù)操作。在你不熟悉某個領(lǐng)域的時候大可不必按部就班的這樣做,直接通過業(yè)務(wù)場景和流程梳理出核心的細粒度業(yè)務(wù)活動點即可。業(yè)務(wù)活動上自然會附著有對象和狀態(tài)。
將事件分解為這三要素后,再次按業(yè)務(wù)對象進行聚類。比如訂單是一個業(yè)務(wù)對象,將所有和訂單相關(guān)的業(yè)務(wù)事件全部聚合在一起。
在聚類完成后,每一個聚類就是一個獨立的限界上下文。
聚合,限界上下文和子域
如果你按上面步驟將每個聚合劃分為一個個獨立的微服務(wù)。
那么最終的微服務(wù)一定粒度太細。
因為每一個核心的業(yè)務(wù)對象都變成了一個微服務(wù)。
拿一個傳統(tǒng)的供應(yīng)鏈系統(tǒng)來舉例,你會看到采購采購單,采購訂單,物料,供應(yīng)商,框架協(xié)議,入庫單,出庫單,庫存信息,采購計劃等是核心的功能聚合點。那么一個傳統(tǒng)的供應(yīng)鏈系統(tǒng)需要劃分出好幾十個微服務(wù)?
按事件風暴梳理事件并基于對象進行功能聚合本身沒有錯。
但是每個聚合本身不能完全1對1映射到微服務(wù)上面。
因此應(yīng)該是將多個相關(guān)的聚合歸并到一起,并以此來劃分限界上下文。限界上下文可以只包含一個聚合,也可以包含多個聚合。
那么子域和限界上下文什么關(guān)系?
一般來講子域和限界上下文之間是對應(yīng)的關(guān)系,但是子域本身也可能是將多個限界上下文合并在一起,形成一個大的子域。
但是不論什么情況。都應(yīng)該意識到不能按單獨的一個個聚合來劃分微服務(wù)。
真正的問題關(guān)鍵不是聚合,而是如何將多個聚合劃分到一個限界上下文里面。
問題關(guān)鍵-如何將多個聚合劃分到一個上下文或子域
在我自己以前做企業(yè)IT架構(gòu)規(guī)劃的時候,在進行業(yè)務(wù)架構(gòu)規(guī)劃的時候就經(jīng)常會遇到業(yè)務(wù)域劃分的問題。
其中一個常用的方法就是參考業(yè)界常用的模型,比如供應(yīng)鏈領(lǐng)域有標準的Scor模型可以參考。整個企業(yè)完整業(yè)務(wù)你也可以參考價值鏈模型。如果你是做電信行業(yè)項目,則有標準的eTom模型可以參考。而產(chǎn)品研發(fā)則完全可以參考IPD模型。
所以參考業(yè)界標準模型是常用的一個劃分業(yè)務(wù)域的方法。
其次,IT人員做架構(gòu)一定不能脫離企業(yè)真實的業(yè)務(wù)組織架構(gòu)和業(yè)務(wù)運作。
還是拿采購類業(yè)務(wù)來說,一個小點的企業(yè)就一個部門供應(yīng)鏈部,會將所有招投標,供應(yīng)商管理,采購詢價,采購,采購執(zhí)行,庫存管理等一系列事情關(guān)閉搞定。但是對于大集團型企業(yè),往往招投標,采購,物流,供應(yīng)商管理等都是獨立的部門。至少也是獨立的科室。
為啥要去觀察企業(yè)本身業(yè)務(wù)組織劃分?
我們實際來觀察下你會發(fā)現(xiàn)。
拿一個企業(yè)來說,這個企業(yè)的采購部內(nèi)部可能經(jīng)常開會或吵架,但是采購部和招投標部之間經(jīng)常就是一些招投標結(jié)果信息的傳遞。也就是企業(yè)在劃分業(yè)務(wù)部門或組織的時候,本身就在考慮如何減少跨部門的交互接口。
這和微服務(wù)拆分里面的微服務(wù)間要盡量解耦是一個道理。
所以再回到多個聚合如何進一步歸類的問題也是同樣道理。
當你觀察的時候,你會發(fā)現(xiàn)供應(yīng)商,物料的管理實際是同一個基礎(chǔ)數(shù)據(jù)管理部門在做。那么這兩個聚合完全是可以劃分到一個限界上下文里面。類似還有采購請購,采購訂單,采購執(zhí)行都是一個部門在做,那么也可以歸到一個上下文。
當然你可以從一個完整業(yè)務(wù)流程分析出發(fā),看究竟分為哪些大的階段,一般來說這些階段點都可以作為微服務(wù)劃分的邊界點。
往往比單純的事件風暴后,再聚合更加有效。比如電商核心業(yè)務(wù)場景和訂單生命周期如下。
當你對核心領(lǐng)域?qū)ο蟮纳芷陔A段梳理清楚后,實際上已經(jīng)可以明確地識別出關(guān)鍵的上下文邊界和關(guān)鍵的領(lǐng)域?qū)ο?。比如上圖可以看到商品,訂單,供應(yīng)商,用戶,庫存都是關(guān)鍵的領(lǐng)域?qū)ο螅嚓P(guān)的業(yè)務(wù)操作也圍繞這些業(yè)務(wù)對象展開。
進一步思考,從聚合到上下文和子域
當談到這里的時候,自然出現(xiàn)了一個新疑惑。
即采用傳統(tǒng)的按業(yè)界參考模型,大的業(yè)務(wù)流程階段邊界,或者企業(yè)本身的業(yè)務(wù)組織劃分等來劃分微服務(wù),這樣劃分出來的微服務(wù)粒度更加合適,也更加容易滿足高內(nèi)聚,松耦合的需求。那么為何還要采用領(lǐng)域建模去劃分微服務(wù)?
因此從聚合到上下文,這個邏輯如何從技術(shù)上去走通成了問題的關(guān)鍵。
即各個聚合之間本身還有耦合性,我如何去分析哪些聚合應(yīng)該進一步歸類到一個限界上下文里面,這有無一種技術(shù)層面的分析方法?
在這里談下我個人的一些思考。
基于核心業(yè)務(wù)活動進行聚合
核心業(yè)務(wù)活動一般指最終帶來明確業(yè)務(wù)價值的活動。
舉例來說采購訂單是核心業(yè)務(wù)活動,而采購申請,框架協(xié)議等是輔助類活動,或前導(dǎo)類活動,最終目的都是形成可執(zhí)行的采購訂單。
付款是核心業(yè)務(wù)活動,而付款申請則是輔助活動,最終為付款服務(wù)。
基于這個思路就可以將諸多的輔助業(yè)務(wù)活動,支撐活動等圍繞核心業(yè)務(wù)活動的多個聚合進行歸類,形成上下文邊界。
基于上層業(yè)務(wù)分析底層聚合之間的耦合性
拿供應(yīng)商和物料兩個聚合來說,為何建議放到一個上下文和微服務(wù)里面。
如上圖,在分析業(yè)務(wù)場景的時候你會發(fā)現(xiàn),對于其它聚合根和供應(yīng)商,物料之間的依賴關(guān)系不是點對點的依賴,而是需要供應(yīng)商和物料聚合后的信息。
供應(yīng)商和物料兩個對象本身沒有耦合性。
當時上層的業(yè)務(wù)場景讓兩個對象之間產(chǎn)生了明顯的耦合性。在這種情況下就需要將其劃分到一個限界上下文里面去。
由于在一個上下文,后續(xù)微服務(wù)劃分對應(yīng)一個數(shù)據(jù)庫,自然也就減少了上層的多次API接口服務(wù)調(diào)用后的應(yīng)用層組裝操作。
希望以上兩點思路對你在劃分上下文邊界的時候有所幫助。
再次總結(jié)下本文想說明的一些關(guān)鍵內(nèi)容如下。
1.微服務(wù)設(shè)計到功能和數(shù)據(jù)兩個層面的劃分
2.事件風暴到聚合,多個聚合如何劃分到一個上下文是難點
3.基于核心業(yè)務(wù)活動和聚合間耦合性是常用方法
當然,這篇文章還有很多沒有思考完善的地方,也歡迎大家提出不同意見并進一步探討,方便我后續(xù)對微服務(wù)劃分思路進一步優(yōu)化改進。