首頁 > 俗語

DDD - 如何理解限界上下文

作者:由 社長大人Robynn 發表于 俗語日期:2022-02-27

理解上下文是什麼意思

理解限界上下文的定義

什麼是限界上下文(Bounded Context)?讓我們來讀一個句子:

wǒ yǒu kuài dì

到底是什麼意思?

DDD - 如何理解限界上下文

我們能確定到底是哪個意思嗎?確定不了!!! 我們必須結合說話人的語氣與語境來理解,例如:

wǒ yǒu kuài dì,zǔ shàng liú xià lái de → 我有塊地,祖上留下來的。

wǒ yǒu kuài dì,shùn fēng de → 我有快遞,順豐的。

在日常的對話中,說話的語氣與語境就是幫助我們理解對話含義的上下文(Context)。當我們在理解系統的領域需求時,同樣需要藉助這樣的上下文,而限界上下文的含義就是用一個清晰可見的邊界(Bounded)將這個上下文勾勒出來,如此就能在自己的邊界內維持領域模型的一致性與完整性。Eric Evans 用細胞來形容限界上下文,因為“細胞之所以能夠存在,是因為細胞膜限定了什麼在細胞內,什麼在細胞外,並且確定了什麼物質可以透過細胞膜。”這裡,細胞代表上下文,而細胞膜代表了包裹上下文的邊界。

因此,若要理解限界上下文,就需要從 Bounded 與 Context 這兩個單詞的含義來理解,Context 表現了業務流程的場景片段。整個業務流程由諸多具有時序的活動組成,隨著流程的進行,不同的活動需要不同的角色參與,並導致上下文因為某個活動的產生隨之發生切換。因而,上下文(Context)其實是動態的業務流程被邊界(Bounded)靜態切分的產物。

假設有這樣一個業務場景:我作為一名諮詢師從成都出發前往深圳為客戶做領域驅動諮詢,無論是從家乘坐地鐵到達成都雙流機場,還是乘坐飛機到達深圳寶安,再從寶安機場乘坐計程車到達酒店,我的身份都是一名乘客(Passenger),雖然因為交通工具的不同,參與的活動也不盡相同,但無論上車、下車,還是辦理登機手續、安檢、登機和下機等活動,終歸都與交通出行有關。那麼,我坐在交通工具上就一定代表我屬於這個上下文嗎?未必!注意在交通出行上下文中,其實模糊了“我”這個概念,強調了“乘客”這個概念,這是參與到該上下文的角色(Role),或者說“身份”。

例如,我在飛機上,忽然想起給客戶提供的諮詢方案還需要完善,於是我拿出電腦,在一萬米高空上繼續思考我的領域驅動設計方案,這時的我雖然還在飛機上,身份卻切換成了一名諮詢師(Consultant)。當我作為乘客乘坐計程車前往酒店,併到前臺辦理入住手續時,我又“撕下了乘客的面具”,搖身一變成為了酒店的賓客(Guest)。次日早晨,我在酒店餐廳用完早餐後,離開酒店前往客戶公司。隨著我走出酒店這個活動的發生,酒店上下文又切換回交通出行。當我到達客戶所在地時,面對客戶,我開始以一名諮詢師身份與客戶團隊交談,瞭解他們的諮詢目標與現有痛點。我制定諮詢計劃與方案,並與客戶一起評審諮詢方案,這時的上下文就切換為諮詢工作了。巧合的是,無論是交通出行還是酒店,都需要支付費用,支付的費用雖然不同,支付的行為也有所差別,需要用到的領域知識卻是相同的,因此這個活動又可以歸為支付上下文。

上下文在流程中的切換猶如電影畫面的場景切換,相同的人物扮演了不同的角色,在不同的上下文參與了不同的活動。由於活動的目標發生了改變,履行的職責亦有所不同,上述場景如下圖所示:

DDD - 如何理解限界上下文

整個業務流程由諸多活動(Actions)組成,參與這些活動的有不同的角色。在每一個上下文中,角色與角色之間透過活動產生協作,以滿足業務流程的需求。這些活動是分散的,活動的目標也不相同,但在同一個上下文中,這些活動卻是為同一個目標提供服務。

因此,在理解限界上下文時,我們需要重視幾個關鍵點:

知識:不同的限界上下文需要的領域知識是不相同的,這實則就是業務相關性,參與到限界上下文中的活動也與“知識”有關。如果執行該活動卻不具備對應知識,則說明對活動的分配不合理;如果該活動的目標與該限界上下文保持一致,卻缺乏相應知識,則說明該活動需要與別的限界上下文協作。

角色:一定要深入思考參與到這個上下文的物件究竟扮演了什麼樣的角色,以及角色與角色在這個上下文中是如何協作的。

邊界:限界上下文按照不同關注點進行分離,各自的邊界則根據耦合關係的強弱來確定,越是關係最弱的地方,越是需要劃定邊界。

我們需要根據業務相關性、耦合的強弱程度、分離的關注點對這些活動進行歸類,找到不同類別之間存在的邊界,這就是限界上下文的含義。上下文(Context)是業務目標,限界(Bounded)則是保護和隔離上下文的邊界,避免業務目標的不單一而帶來的混亂與概念的不一致。

理解限界上下文的價值

Eric Evans 是在戰略設計中引入限界上下文概念的,他認為:

既然無法維護一個涵蓋整個企業的統一模型,那就不要再受到這種思路的限制。透過預先決定什麼應該統一,並實際認識到什麼不能統一,我們就能夠建立一個清晰的、共同的檢視,然後需要用一種方式來標記出不同模型之間的邊界和關係。 為了解決多個模型的問題,我們需要明確地定義模型的範圍——模型的範圍是軟體系統中一個有界的部分,這部分只應用一個模型,並儘可能使其保持統一。團隊組織中必須一致遵守這個定義。 明確地定義模型所應用的上下文。根據團隊的組織、軟體系統的各個部分的用法以及物理表現(程式碼和資料庫模式等)來設定模型的邊界,在這些邊界中嚴格保持模型的一致性,而不要受到邊界之外問題的干擾和混淆。

基於以上引用的三段描述,我們可以清晰地勾勒出 Eric Evans 對於限界上下文的著眼點,那就是對邊界的控制。倘若將上下文視為一國,則領域之王就應該捍衛國土疆域,國界內的一寸一尺之地都是神聖不可侵犯的。因而,我們要理解限界上下文的價值,就須得從邊界來理解。

觀察角度的不同,限界上下文劃定的邊界也有所不同。大體可以分為如下三個方面:

領域邏輯層面:限界上下文確定了領域模型的業務邊界,維護了模型的完整性與一致性,從而降低系統的業務複雜度。

團隊合作層面:限界上下文確定了開發團隊的工作邊界,建立了團隊之間的合作模式,避免團隊之間的溝通變得混亂,從而降低系統的管理複雜度。

技術實現層面:限界上下文確定了系統架構的應用邊界,保證了系統層和上下文領域層各自的一致性,建立了上下文之間的整合方式,從而降低系統的技術複雜度。

這三種邊界體現了限界上下文對不同邊界的控制力。業務邊界是對領域模型的控制,工作邊界是對開發協作的控制,應用邊界是對技術風險的控制。引入限界上下文的目的,其實不在於如何劃分邊界,而在於如何控制邊界。

我曾經有機會向 EventStorming 的創始人 Alberto Brandolini 請教他對限界上下文的理解,他做了一個非常精彩的總結:bounded context are a mean of safety(限界上下文意味著安全)。這裡的 safety 做何解呢?他的意思是:being in control and no surprise,對限界上下文是可控制的,就意味著你的系統架構與組織結構都是可控的;沒有出乎意料的驚訝,雖然顯得不夠浪漫,但其實只有這樣才能使得團隊避免過大的壓力。Alberto 告訴我:

Surprise leads to stress and stress leads to no learning, just hard work。 (出乎意料的驚訝會導致壓力,而壓力就會使得團隊疲於加班,缺少學習。)

這是真正看破限界上下文字質的大師高論!顯然,限界上下文並不是像大多數程式設計師理解的那樣,是模組、服務、元件或者子系統,而是你對領域模型、團隊合作以及技術風險的控制。在《Entity Framework 模型在領域驅動設計限界上下文中的應用》一文中,作者 Juelie Lerman 認為:“當開發一個具有大型領域模型的超大規模的應用程式時,與設計一個單一的大領域模型相比,將大領域模型根據應用程式的業務需要“切割”成一系列較小的模型是非常重要的,我們也往往能夠從中獲得更多的好處。”她還提到:“更小的模型為我們的軟體設計和開發帶來了更多的好處,它使得團隊能夠根據自己的設計和開發職責確定更為明確的工作邊界。小的模型也為專案帶來了更好的可維護性:由於上下文由邊界確定,因此對其的修改也不會給整個模型的其他部分造成影響。”顯然,透過限界上下文對領域模型進行分解,就能保證在其邊界內建立的模型內聚性更高,在邊界隔離下,受到變化的影響也更小,反映為團隊合作的工作邊界,就更容易保證團隊之間的溝通與協作。

限界上下文是“分而治之”架構原則的體現,我們引入它的目的其實為了控制(應對)軟體的複雜度,它並非某種固定的設計單元,我們不能說它就是模組、服務或元件,而是透過它來幫助我們做出高內聚低耦合的設計。只要遵循了這個設計,則限界上下文就可能成為模組、服務或元件。所以,文章《Bounded Contexts as a Strategic Pattern Beyond DDD》才會寫到:“限界上下文體現的是高層的抽象機制,它並非程式語言或框架的產出工件,而是體現了人們對領域思考的本質。”

宋代禪宗大師青原行思提出參禪的三重境界:

參禪之初:看山是山,看水是水;

禪有悟時:看山不是山,看水不是水;

禪中徹悟:看山仍然山,看水仍然是水。

我覺得理解限界上下文與模組、服務或元件的關係,似乎也存在這三重境界:

參悟之初:模組、服務或元件就是限界上下文。

當有悟時:模組、服務或元件不是限界上下文。

徹底悟透:模組、服務或元件仍然是限界上下文。

能理解嗎?——更糊塗了!好吧,以上三重境界純屬忽悠,還是讓我上一點乾貨吧。注意了,我要提到一個重要的概念,就是“自治”,拋開模組、服務或元件對你的影響,請大家先把限界上下文看做是一個“自治”的單元。所謂“自治”就是滿足四個特徵:最小完備、穩定空間、自我履行、獨立進化。如下圖所示的自治單元就是限界上下文,對映到編碼實現,則可能是模組、元件或服務:

DDD - 如何理解限界上下文

最小完備是實現“自治”的基本條件。所謂“完備”,是指自治單元履行的職責是完整的,無需針對自己的資訊去求助別的自治單元,這就避免了不必要的依賴關係。而“最小完備”則進一步地限制了完備的範圍,避免將不必要的職責被錯誤地新增到該自治單元上。對於限界上下文而言,就是要根據業務價值的完整性進行設計。例如,對於支付上下文,其業務價值就是“安全地完成線上支付業務”,那麼在確定限界上下文的時候,就應該以完成該業務價值的最小功能集為設計邊界。

自我履行意味著由自治單元自身決定要做什麼。從擬人的角度來思考,就是這些自治單元能夠對外部請求做出符合自身利益的明智判斷,是否應該履行該職責,由限界上下文擁有的資訊來決定。例如,可以站在自治單元的角度去思考:“如果我擁有了這些資訊,我究竟應該履行哪些職責?”這些職責屬於當前上下文的活動範圍,一旦超出,就該毫不猶豫地將不屬於該範圍的請求轉交給別的上下文。例如,在當訂單上下文履行了驗證訂單的職責之後,需要執行支付活動時,由於與支付相關的業務行為要操作的資訊已經超出了訂單上下文的範疇,就應該將該職責轉移到支付上下文。自我履行其實意味著對知識的掌握,為避免風險,你要履行的職責一定是你掌握的知識範疇之內。

穩定空間指的是減少外界變化對限界上下文內部的影響。自治的設計就是要劃定分屬自己的穩定空間,讓自治單元擁有空間內的掌控權,保持空間的私密性,開放空間介面應對外部的請求。劃分自治空間,需要找到限界上下文之間的間隙處,然後依勢而為,沿著間隙方向順勢劃分,而所謂“間隙”,其實就是依賴最為薄弱之處。例如,在電商系統中,管理商品上架、下架與評價商品都與商品直接相關,但顯然評價商品與商品的依賴關係更弱。倘若需要分解限界上下文,保證上下文的穩定性,就可以將評價商品的職責從商品上下文中分離出去,但卻不能分離商品上架和下架功能。穩定空間符合開放封閉原則(OCP),即對修改是封閉的,對擴充套件是開放的,該原則其實體現了一個單元的封閉空間與開放空間。封閉空間體現為對細節的封裝與隱藏,開放空間體現為對共性特徵的抽象與統一,二者共同確保了整個空間的穩定。

獨立進化與穩定空間剛好相反,指的是減少限界上下文的變化對外界的影響。如果借用限界上下文的上下游關係來闡釋,則穩定空間寓意下游限界上下文,無論上游怎麼變,我自巋然不動;獨立進化寓意上游限界上下文,無論下游有多少,我凌寒獨自開。實現上看,要做到獨立進化,就必須保證對外公開介面的穩定性,因為這些介面往往被眾多消費者使用,一旦修改,就會牽一髮而動全身。一個獨立進化的限界上下文,需要介面設計良好,符合標準規範,並在版本上考慮了相容與演化。

自治的這四個要素是相輔相成的。最小完備意味著職責是完備的,從而減少了變化的可能;自我履行意味著自治單元能夠智慧地判斷行為是否應該由其履行,當變化發生時,也能聰明審慎地做出合理判斷;穩定空間透過隱藏細節和開放抽象介面來封裝變化;獨立進化則透過約束介面的規範與版本保證內部實現的演化乃至於對實現進行全面地替換。最小完備是基礎,只有賦予了限界上下文足夠的資訊,才能保證它的自我履行。穩定空間與獨立進化則一個對內一個對外,是對變化的有效應對,而它們又是透過最小完備和自我履行來保障限界上下文受到變化的影響最小。

這四個要素又是高內聚低耦合思想的體現。我們需要根據業務關注點和技術關注點,儘可能將強相關性的內容放到同一個限界上下文中,同時降低限界上下文之間的耦合。對於整個系統架構而言,不同的限界上下文可以採用不同的架構風格與技術決策,而在每個限界上下文內部保持自己的技術獨立性與一致性。由於限界上下文邊界對技術實現的隔離,不同限界上下文內部實現的多樣性並不會影響整體架構的一致性。

分享交流:

我們為分享交流建立了微信交流群,以方便更有針對性地討論課程相關問題。入群方式請新增作者微訊號:「Robynn-D」,並註明「DDD」,謝謝~

本文首發:

DDD - 如何理解限界上下文