講真,別再拿著聚合尋找限界上下文了

聚合分組法和它的問題

在事件風暴工作坊中,常用的劃分限界上下文的方法是:

對前一步(事件風暴)產生的聚合進行分組,通過業(yè)務的內聚性和關聯(lián)度劃分邊界,結合限界上下文的定義進行判斷,并給出上下文名稱。

[服務化設計階段路徑方案]

我將其稱之為“聚合分組法”。然而面對一堆聚合,要得出一套合理的分組是非常困難的:

  1. “相關性”全憑經驗

    相關性是一個過于抽象的規(guī)則,非常依賴經驗。

    舉個例子。在一個活動運營系統(tǒng)中,有“注冊獎勵活動”、“注冊獎勵規(guī)則”、“任務獎勵活動”、“任務獎勵規(guī)則”等概念。是把所有的“活動”分為一組,所有“規(guī)則”分為一組,還是把“注冊”相關的分為一組,把“任務”相關的分為一組?這是個讓人頭疼的問題。也許你會說需要業(yè)務人員的輸入,但是業(yè)務人員很可能只會告訴你這些概念之間都有關系。

  2. 不健康的聚合上下文

    聚合分組法很容易導向一種按照聚合劃分的架構。服務圍繞聚合建設,而非針對某個業(yè)務價值,也就無法提供正確的業(yè)務價值。圍繞聚合建設的服務,看上去可以復用,但是會造成服務間的緊耦合,容易成為最糟糕的分布式單體架構:

    當架構是分布式單體時,往往需要同時修改多個服務,同時部署多個服務、服務之間調用非常頻繁。

    [You’re Not Actually Building Microservices]

    聚合分組法也無法很好的識別“重復的概念”問題([領域驅動設計]14.1,指某一個概念,應該被設計成多個模型,因為它們有不同的規(guī)則,甚至有不同的數(shù)據(jù))。使用聚合分組法往往導致把帶著這樣的聚合簡單的放到某個限界上下文中。

  3. 隱藏的劃分方案

    還很可能是這種情況:在使用聚合分組法時,架構師已經有一個隱藏在心里的模糊的劃分方案,在劃分限界上下文時都是往該方案上靠。但是由于這個劃分方案只是模糊存在于架構師的腦中,并沒有拿出來討論,很可能經不起推敲,最終無法言說,淪為“by experience”。

如何劃分限界上下文

如何劃分限界上下文?在回答這個問題前,讓我們先看看限界上下文到底是什么。

在[領域驅動設計]第14章提出了著名的限界上下文。限界上下文是為了分解大型模型:

然而在幾乎所有這種規(guī)模的組織中,整個業(yè)務模型太大且過于復雜以至于難以管理,甚至很難把它作為一個整體來理解。我們必須把系統(tǒng)分解為較小的組成部分,無論在概念還是在實現(xiàn)上。

有時,企業(yè)系統(tǒng)會集成各種不同來源的子系統(tǒng),或者包含諸多屬于完全不同領域的應用程序。要把這些不同部分中隱含的模型統(tǒng)一起來是不可能的。通過為每個模型顯式地定義一個限界上下文,然后在必要的情況下定義它與其他上下文的關系,建模人員就可以避免模型變得混亂。

[領域驅動設計 第四部分] (https://book.douban.com/subject/26819666/)

bounded-context

限界上下文告訴我們,同一個概念,不必總是對應于一個單一模型,也可以對應于多個模型。用限界上下文明確模型要解決的問題,可以保持每個模型的清晰。限界上下文是領域模型的邊界,也就是領域知識的邊界。和上下文主題緊密相關的模型內聚在上下文內,而其他模型被會分到其他限界上下文中。限界上下文內的領域知識是高內聚低耦合的。

bounded-context-2

限界上下文的主題是什么呢?我認為是子域。每個限界上下文專注于解決某個特定的子域的問題。每個子域都對應一個明確的問題,提供獨立的價值,所以每個子域都相對獨立。子域及其對應的限界上下文中的模型會因為其要解決的問題變化而變化,不會因為其他子域的變化而變化,即低耦合;當一個子域發(fā)生變化時,只需要修改其對應限界上下文中的模型,不需要變動其他子域的模型,即高內聚。

Evans也談論了限界上下文和子域的關系:

One confusion that Evans sometimes notices in teams is differentiating between bounded contexts and subdomains. In an ideal world they coincide, but in reality they are often misaligned.

Evans有時會在團隊中發(fā)現(xiàn)的一個困惑,就是如何區(qū)分限界上下文和子域。在理想的世界中它們是重合的,但在現(xiàn)實世界中它們常常是錯位的。

[Defining Bounded Contexts — Eric Evans at DDD Europe]

當我們設計一個新系統(tǒng)或者設計遺留系統(tǒng)的目標架構時,我們往往會按照理想的方式進行設計。而在理想情況下,子域和限界上下文是重合的。

[領域驅動設計精粹]中也講述了一個通過尋找核心域相關的概念來識別限界上下文的方法。

如何分解子域

根據(jù)子域來識別限界上下文,那么子域如何得到呢?我們通過分解問題域的方式,將整個問題域分解成若干個更小、更簡單、更容易解決的問題子域。

我們需要某種方法,將領域分解成邏輯上相互獨立且沒有交叉的子域。在這里的方法是通過產品愿景,識別核心域,進而識別核心域周邊的子域。

識別核心域

由于核心域是最明顯、最容易識別出來的子域,所以我們先從核心域開始。

每一個子域甚至每一個領域模型都是為了產品愿景而存在的。我們分解子域的第一步,就是從產品愿景中獲取核心域。產品愿景包含“相對抽象的產品價值”,以及“實現(xiàn)該價值的主要功能”。其中,主要功能就是我們尋找核心域的依據(jù)。想象一下,如果要做MVP的話,我們會挑選最能夠提供其核心價值的功能來開發(fā),以驗證產品價值。MVP往往就是核心域。

以上述活動運營系統(tǒng)為例,其產品愿景是通過各種吸引用戶的優(yōu)惠活動,以幫助客戶通過活動提升用戶量和知名度。其核心域是給客戶提供吸引用戶的多樣的靈活的活動,包括活動形式、活動規(guī)則和多種獎勵。

識別核心域周邊的子域

核心域識別出來了,接下來就是識別核心域周邊的子域。核心域往往不會獨立存在,會有其他子域同核心域一起才能達成業(yè)務目標。這里需要回答的問題是:

  • 有哪些子域是用來支撐核心域的?

    這些子域是幫助核心域更好的工作。例如提供審批流程以配置核心域,提供各種輔助功能更好的為核心域提供內容。

  • 有哪些子域是核心域衍生出來的?

    核心域經常會產生一些數(shù)據(jù),這些數(shù)據(jù)也有其價值。比如產生各種報表,活動獎勵的發(fā)放記錄。

  • 有哪些子域是用來支撐或衍生自這些新識別出的子域的?

    用來支撐核心域的子域、以及核心域衍生的子域,也有各自的支撐子域和衍生子域。

活動運營系統(tǒng)

識別出來的每個子域只對應一個問題,子域之間是相互獨立的,沒有交叉,不是包含關系。所以子域加起來就是整個領域。

也可以通過角色、時間等因素分解子域。解決不同角色的問題可能分屬不同子域,比如用戶參與活動、運營人員配置活動分屬不同子域,兩個子域的變化原因不同;不同時間使用的功能可能屬于不到子域,比如先有運營人員配置活動,再有用戶參與活動,配置活動和參與活動分屬不同子域。

如果按照聚合分組劃分限界上下文,很可能出現(xiàn)“活動上下文”,同時活動模型,即承擔運營人員配置的職責,又承擔用戶參與規(guī)則校驗的職責,這會導致職責過多,違背了單一職責。另外活動規(guī)則校驗的模塊需要支持高并發(fā),需要使用和配置模塊不同的技術架構。如果這些相似的概念和不同的技術實現(xiàn)屬于不同的上下文,就可以保持各自模型的完整,技術上也可以做到獨立演進。

子域的粒度

理論上子域仍然可以被分解。例如活動子域可以分解為活動參與規(guī)則子域、獎勵子域等。那么子域粒度多大是合適的呢?

我們希望每個子域可以解決某個特定的問題,讓這個問題的解決方案都內聚在子域對應的限界上下文內,所以如果問題的再分解沒有的邊界并不清晰,建議先不分解。隨意的拆分會導致成為“分布式單體”。

識別限界上下文

一個限界上下文封裝了一個相對獨立子領域的領域模型和服務。

子域subdomain和限界上下文某種意義上是互相印證的

DDD戰(zhàn)術篇:領域模型的應用

這個時候我們通過事件風暴得到的領域模型就可以出場了。領域模型和子域都是從業(yè)務知識里分析得到的,將兩者匹配起來可以再次驗證我們對于業(yè)務的理解、子域的分解和領域模型是否合理。

為每個子域創(chuàng)建一個解決其問題的限界上下文,然后為每個領域模型找到其歸屬的限界上下文。每個領域事件都是為了解決某個問題,它和它相關的領域模型就應該放在這個問題子域對應的限界上下文里。

比如“活動已上線“這個事件,由運營人員在配置時觸發(fā),會導致用戶可以開始參與活動。那么這個事件及其對應的“活動”概念應該被分為兩個模型,分別歸屬于活動配置子域對應的“活動配置上下文”和活動子域對應的“活動上下文”。

為領域模型尋找歸屬完成后,我們會發(fā)現(xiàn)這么幾個情況。

  • 同一個概念可能會出現(xiàn)在多個限界上下文中。發(fā)生這種情況很正常,說明這多個子域都需要這個概念,而且很可能不同子域的領域模型不完全相同。

    比如剛才說到“活動”既存在于“活動上下文”中,又在“活動配置上下文”中。這里我們就很好的識別出了“重復的概念”問題。

  • 也有一些概念重復在多個限界上下文中,這些概念和該上下文的主題并沒有緊密的關系。這些模型可以單獨出一個限界上下文,用以同時支撐多個限界上下文,以減輕限界上下文的負擔。

  • 有時候某個模型找不到合適的限界上下文,說明很可能是遺漏了一個子域,那就需要回到“分解子域”步驟,重新審視產品愿景。


聚合分組法采用“相關性”來劃分限界上下文,其問題在于缺少一個主題,而子域恰好可以用來提供這個主題。本文的“愿景”-“核心域”-“周邊子域”方法,不是唯一分解問題域的方法,任何可以將領域分解成高內聚低耦合的子域的方法都是可行的方法。


更多精彩洞見,請關注:ThoughtWorks洞見

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容