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