GitChat課程《領域驅(qū)動設計--戰(zhàn)略篇》筆記,課程作者張逸
一.理解限界上下文
1.限界上下文的定義
- 限界上下文:Bounded Context
1)上下文(Context)表現(xiàn)業(yè)務流程的場景片段
2)整個業(yè)務流程由諸多具有時序的活動組成蓄喇,隨著流程的進行发侵,不同的活動有不同的角色參與,并導致上下文切換
3)上下文(Context)其實是動態(tài)的業(yè)務流程被邊界(Bounded)靜態(tài)切分的產(chǎn)物 - 以咨詢師從成都到深圳為客戶提供服務的場景為例理解限界上下文
1)相同的人物在不同的上下文參與不同的活動妆偏,履行不同的職責
2)整個業(yè)務流程由諸多分散且目標不同的活動(Actions)組成刃鳄,這些活動在同一個上下文中為同一目標提供服務
- 理解限界上下文的關鍵點
1)知識:不同的限界上下文需要不同的領域知識,這實際上就是業(yè)務相關性
2)角色:參與到這個上下文的對象扮演何種角色钱骂,各種角色如何協(xié)作
3)邊界 - 根據(jù)業(yè)務相關性叔锐、耦合強弱程度、分離的關注點對業(yè)務的活動進行歸類见秽,找到不同類別之間存在的邊界愉烙,這就是限界上下文的含義
2.限界上下文的價值
- 對不同邊界的控制力
1)領域邏輯層面:限界上下文確定了領域模型的業(yè)務邊界,維護了模型的完整性與一致性解取,從而降低系統(tǒng)的業(yè)務復雜度
2)團隊合作層面:限界上下文確定了開發(fā)團隊的工作邊界齿梁,建立了團隊之間的合作模式,避免團隊之間的溝通混亂肮蛹,從而降低系統(tǒng)開發(fā)的管理復雜度
3)技術實現(xiàn)層面:限界上下文確定了系統(tǒng)架構(gòu)的應用邊界勺择,保證了系統(tǒng)層和上下文領域?qū)痈髯缘囊恢滦裕⒘松舷挛闹g的集成方式伦忠,從而降低系統(tǒng)的技術復雜度 - 限界上下文是滿足下述四個特點的自治單元
1)最小完備:自治單元履行的職責是完整的省核,同時避免添加不必要的職責
2)自我履行:由自治單元自身決定要做什么,對于不屬于自身的行為應轉(zhuǎn)交給其他上下文
3)穩(wěn)定空間:減少外界變化對限界上下文內(nèi)部的影響
4)獨立進化:減少限界上下文變化對外界的影響
3.限界上下文的控制力
- 限界上下文分離了業(yè)務邊界
如在電商系統(tǒng)中昆码,產(chǎn)品實體Product在不同的限界上下文有不同的含義气忠,關注的屬性與行為也不盡相同
1)在采購上下文邻储,需要關注產(chǎn)品的進價、最小起訂量與供貨周期
2)在市場上下文旧噪,則關心產(chǎn)品的品質(zhì)吨娜、售價,以及用于促銷的精美?片和銷售類型
3)在倉儲上下文淘钟,倉庫?作?員更關心產(chǎn)品的位置宦赠,重量與體積,是否易碎品以及訂購產(chǎn)品的數(shù)量
4)在推薦上下文米母,系統(tǒng)關注的是產(chǎn)品的類別勾扭、銷量、收藏數(shù)铁瞒、正面評價數(shù)妙色、負面評價數(shù)。
理想的設計方案是讓每一個限界上下文擁有自己的領域模型Product
- 限界上下文明確了工作邊界
1)2PTs(Two-Pizza Teams)規(guī)則:讓團隊保持在兩個披薩能讓成員吃飽的規(guī)模(7-10人)
2)康威定律(Conway's Law):任何組織在設計一套系統(tǒng)時慧耍,所交付的設計方案在結(jié)構(gòu)上都與該組織的溝通結(jié)構(gòu)保持一致
3)DDD按照軟件的特性(Feature)而非組件(Component)來組織軟件開發(fā)團隊
4)組件團隊:數(shù)據(jù)庫+前端+后端身辨,更容易發(fā)揮每個人的技能特長,但導致團隊成員缺乏對業(yè)務的了解芍碧,任何修改都要橫跨多個組件團隊煌珊,溝通成本較高
5)特性團隊:端對端的開發(fā)垂直細分領域的跨職能團隊,將需求分析师枣、架構(gòu)設計怪瓶、開發(fā)測試等多個角色糅合在一起萧落,專注于領域邏輯
- 限界上下文封裝了應用邊界
劃分限界上下文不只從業(yè)務邊界確立践美,還要考慮控制技術復雜度,從而保證系統(tǒng)質(zhì)量找岖,例如
1)高并發(fā):外賣系統(tǒng)的訂單業(yè)務與門店陨倡、支付等領域存在業(yè)務相關性,但外賣訂單在高峰期存在高并發(fā)壓力许布,將訂單業(yè)務作為一個單獨的限界上下文兴革,可以從物理架構(gòu)上保證獨立性,在資源分配上做到高優(yōu)先級地擴展
2)功能重用:?個?向企業(yè)雇員的國際報稅系統(tǒng)蜜唾,報稅業(yè)務杂曲、旅游業(yè)務與 Visa 業(yè)務都需要賬戶功能的?撐。系統(tǒng)對?戶的注冊與登錄有較為復雜的業(yè)務處理流程袁余。從功能重用的角度考慮擎勘,應該將賬戶管理作為一個單獨的限界上下文,以滿足核心領域?qū)~戶管理的功能重用
3)實時性:電商系統(tǒng)中颖榜,價格是商品概念的一個重要屬性棚饵,僅僅從業(yè)務角度考慮煤裙,在進行領域建模時,價格僅僅是一個普通的領域值對象噪漾,但電商系統(tǒng)的商品數(shù)量達到數(shù)十億種硼砰,每天獲取商品信息的調(diào)用量在峰值達到數(shù)億乃至數(shù)百億次時,價格就從業(yè)務問題變成了技術問題欣硼。為了保證高并發(fā)下價格的實時性题翰,可以將價格領域作為一個獨立的限界上下文,形成自己與眾不同的架構(gòu)方案分别,以滿足高并發(fā)和實時性的要求
4)第三方服務集成:電商系統(tǒng)需要支持多種常見的支付渠道遍愿,如微信、支付寶耘斩、銀聯(lián)沼填。可以將支付服務集成劃分為一個單獨的限界上下文括授,一方面為支付服務額客戶端提供完全統(tǒng)一的支付接口坞笙,保證接口調(diào)用上的便利性與一致性,另一方面解除第三方支付服務與電商系統(tǒng)內(nèi)部模塊之間的耦合荚虚,避免“供應商鎖定”
5)遺留系統(tǒng):將遺留系統(tǒng)劃分為一個限界上下文薛夜,由于新增需求與原有系統(tǒng)在業(yè)務上存在交叉功能,因而可能失去部分代碼的重用機會版述,但可以避免在新功能開發(fā)過程中陷入遺留系統(tǒng)龐大代碼庫的泥沼
二.識別限界上下文
- 從業(yè)務邊界梯澜、工作邊界、應用邊界三個層次識別
1.從業(yè)務邊界識別限界上下文
- 在梳理主要業(yè)務流程后渴析,抽象出不同的業(yè)務場景晚伙,最后總結(jié)出業(yè)務活動的描述
- 從語義相關性分析業(yè)務活動的描述,如果是相同的語義俭茧,可以作為歸類的特征
- 從功能角度去分析業(yè)務活動咆疗,如果存在關聯(lián)和依賴,可以作為歸類的特征
2.從工作邊界識別限界上下文
- 確定限界上下文合理的工作粒度
- 團隊之間是“滲透性邊界”母债,不能太封閉(拒絕外部輸入)午磁,也不能太開放(失去內(nèi)聚力)
3.從應用邊界識別限界上下文
- 質(zhì)量屬性:利用限界上下文將可能改變系統(tǒng)架構(gòu)的風險控制在一個極小范圍內(nèi)
- 重用和變化
1)運用重用原則分離的限界上下文對應于支撐子領域,作為上游上下文為其他上下文提供業(yè)務支撐
2)一個限界上下文不應該存在兩個引起它變化的原因 - 遺留系統(tǒng):對遺留系統(tǒng)中需要替換的組件進行抽象毡们,從而將消費者與遺留系統(tǒng)中的組件實現(xiàn)進行解耦迅皇,最后提供一個新的組件實現(xiàn),在保留抽象層接口不變的情況下替換遺留系統(tǒng)的舊組件衙熔,完成技術棧遷移
三.上下文映射
1.上下文映射概述
- 領域驅(qū)動設計通過上下文映射(Context Map)來討論限界上下文之間的協(xié)作問題
- 上下文映射是一種設計手段登颓,包括
1)共享內(nèi)核(Shared Kernel)
2)防腐層(Anticorruption Layer)
3)開發(fā)主機服務(Open Host Service)等模式 - 兩個限界上下文之間的關系方向由術語上游(UpStream)和下游(DownStream)描述
1)限界上下文影響作用力的方向與程序員慣常理解的依賴方向相反,上游影響下游青责,意味下游依賴于上游
2)下游上下文中的用例才是核心領域挺据,而上游限界上下文是下游限界上下文的功能支撐
3)例如在下訂單場景中取具,訂單上下文調(diào)用支付上下文,支付上下文是提供功能支撐的上游上下文扁耐,訂單上下文是下游上下文暇检,下訂單是核心領域
- 上下文映射模式分為
1)團隊協(xié)作模式:對應于團隊(上下文)合作的工作邊界
2)通信集成模式:從應用邊界的角度分析了限界上下文之間該如何通信才能提升設計質(zhì)量
2.上下文映射模式1:團隊協(xié)作
- 團隊協(xié)作應遵循“各司其職,權責分明”的模式婉称,在滿足合理分配職責的前提下块仆,謹慎地確保每個限界上下文的粒度
- DDD根據(jù)團隊協(xié)作的方式與緊密程度,定義了合作王暗、共享內(nèi)核悔据、客戶方-供應方開發(fā)、遵奉者俗壹、分離方式五種團隊協(xié)作模式
- 合作關系(Partnership)
表示兩個限界上下文的團隊存在要么一起成功要么一起失敗的強耦合關系科汗,甚至是糟糕的雙向依賴,對于這種槽糕的何種關系绷雏,通常有三種解決方法:
1)既然兩個限界上下文存在如此緊密的合作關系头滔,說明與其拆分,不如讓它們合并在一起
2)將產(chǎn)生特性依賴的職責分配到正確的位置涎显,盡力減少一個方向的多于依賴
3)識別產(chǎn)生雙向依賴或循環(huán)依賴的原因坤检,然后將它從各個限界上下文中剝離出來,成為單獨的限界上下文期吓,即所謂的“共享內(nèi)核(Shared Kernel)” - 共享內(nèi)核(Shared Kernel)
共享內(nèi)核用于避免重復早歇。這種重用以犧牲限界上下文自由更改的能力為代價 - 客戶方-供應方開發(fā)(Customer-Supplier Development)
團隊合作中最常見的合作模式,體現(xiàn)的是上游(供應方)與下游(客戶方)的合作關系箭跳。這種合作需要兩個團隊協(xié)商以下問題:
1)下游團隊對上游團隊提出的領域需求
2)上游團隊提供的服務采用什么樣的協(xié)議與調(diào)用方式
3)下游團隊針對上游服務的測試策略
4)上游團隊給下游團隊承諾的交付日期
5)當上游服務的協(xié)議或調(diào)用方式發(fā)生變更時,該如何控制變更 - 遵奉者(Conformist)
客戶方-供應方開發(fā)模式悬襟,是上游團隊滿足下游團隊提出的領域需求衅码;而遵奉者模式拯刁,是由上游團隊來決定是響應還是拒絕下游團隊提出的請求垛玻。遵奉者模式意味著
1)可以直接重用上游上下文的模型(好的)
2)減少了兩個限界上下文之間模型的轉(zhuǎn)換成本(好的)
3)使得下游限界上下文對上游產(chǎn)生了模型上的強依賴(壞的) - 分離方式(Separate Ways)
分離方式的合作模式指兩個限界上下文沒有任何關系
例如在電商網(wǎng)站中账嚎,支付上下文與商品上下文就是分離方式喂江,而貨幣上下文其實是支付上下文
3.上下文映射模式2:通信集成
- 利用防腐層和開放主機服務降低限界上下文之間的耦合關系
- 防腐層(Anticorruption Layer)
1)在架構(gòu)層面,通過引入防腐層有效隔離限界上下文之間的耦合
2)防腐層同時還可以扮演適配器旁振、調(diào)停者获询、外觀等角色
3)防腐層往往屬于下游限界上下文,用以隔絕上游限界上下文可能發(fā)生的變化
- 開放主機服務(Open Host Service)
1)開放主機服務即定義公開服務的協(xié)議拐袜,包括通信的方式吉嚣、傳遞消息的格式(協(xié)議),保證開放的服務不會輕易做出變化
2)防腐層是下游限界上下文對抗上游變化的利器蹬铺,開放主機服務是上游服務用來吸引更多下游使用者的誘餌
3)上游限界上下文往往會被多個下游限界上下文消費尝哆,如果通過防腐層的形式需要為每個下游提供一個相似的防腐層,冗余度高 - 發(fā)布/訂閱事件
1)即使確定了發(fā)布語言規(guī)范的OHS甜攀,仍然會導致兩個上下文之間存在耦合關系较解,下游限界上下文必須知道上游服務的ABC(Address、Binding與Contract)赴邻,對于不同的分布式實現(xiàn)印衔,還需要在下游定義類似服務樁的客戶端
2)發(fā)布/訂閱事件的方式在解耦合方面走得更遠。一個限界上下文作為事件的發(fā)布方姥敛,另外的多個限界上下文作為事件的訂閱方奸焙,二者的協(xié)作通過經(jīng)由消息中間件進行傳遞的事件消息來完成。在確定消息中間件后彤敛,發(fā)布方與訂閱方唯一存在的耦合點就是事件持有的數(shù)據(jù)
3)實例:從買家搜索商品并將商品加入購物車開始与帆,到下訂單、支付墨榄、配送完成訂單結(jié)束玄糟,整個過程通過發(fā)布/訂閱事件方式由多個限界上下文一起協(xié)作完成
四.辨別限界上下文的協(xié)作關系
1.限界上下文的通信邊界對協(xié)作的影響
- 限界上線的通信邊界分為進程內(nèi)邊界與進程間邊界,通信邊界直接影響上下文映射模式的選擇
- 進程間邊界需要考慮跨進程訪問的成本袄秩,如序列化和反序列化阵翎、網(wǎng)絡開銷等。由于跨進程調(diào)用的限制之剧,彼此之間的訪問協(xié)議也不盡相同郭卫,同時還需要控制上游限界上下文可能引入的變化,一個典型的協(xié)作方式是同時引入開放主機服務(OHS)與防腐層(ACL)
1)限界上下文A對外通過控制器(Controller)為用戶界面層暴露REST服務背稼,而在內(nèi)部則調(diào)用應用層的應用服務(Application Service)贰军,然后再調(diào)用領域?qū)拥念I域模型
2)如果限界上下文A需要訪問限界上下文B的服務,則通過領域?qū)拥慕涌?Interface)調(diào)用基礎設施層的客戶端(Client)完成蟹肘,這個客戶端即限界上下文A的防腐層
3)限界上下文B訪問限界上下文C的方式完全一致词疼,限界上下文C則通過資源庫(Repository)接口經(jīng)由持久化(Persistence)組件訪問數(shù)據(jù)庫
2.協(xié)作即依賴
- 兩個限界上下文存在協(xié)作俯树,意味著彼此存在依賴關系,一方需要知道另一方的知識贰盗,包括
1)領域行為:導致行為之間耦合的原因是什么聘萨?如果是上下游關系,要確定下游是否是上游服務的真正調(diào)用者
2)領域模型:需要重用別人的領域模型童太,還是自己重新定義一個模型
3)數(shù)據(jù):是否需要限界上下文對應的數(shù)據(jù)庫提供支撐業(yè)務行為的操作數(shù)據(jù)
這三種知識都將產(chǎn)生依賴
3.領域行為及其產(chǎn)生的依賴
- 領域行為
領域行為在設計層面就是每個領域?qū)ο蟮穆氊熋追氊熆梢杂蓪嶓w(Entity)、值對象(Value Object)书释、領域服務(Domain Service)翘贮、資源庫(Repository)或者工廠(Factory)對象承擔。包括三種履行職責的方式
1)親自完成所有工作
2)請求其他對象幫忙完成部分工作(和其他對象協(xié)作)
3)將整個服務請求委托給另外的幫助對象 - 領域行為產(chǎn)生的依賴
1)當領域?qū)ο舐男新氊煹姆绞綖樯鲜龅暮髢煞N時爆惧,必然牽涉到對象之間的協(xié)作
2)每個領域?qū)ο髴撝怀袚约荷瞄L處理的部分狸页,而將自己不擅長的職責轉(zhuǎn)移到別的對象
3)實例:電商系統(tǒng)業(yè)務場景客戶已經(jīng)選擇好要購買的商品,并通過購物車提交訂單扯再,那么提交訂單的職責應該由客戶上下文還是訂單上下文履行芍耘?
領域行為產(chǎn)生的依賴可以通過抽象接口來解耦 - 實例:下訂單場景的限界上下文架構(gòu)
1)領域?qū)犹幱谙藿缟舷挛牡暮诵?br> 2)應用層包裹整個領域?qū)樱ㄟ^RESTful服務與作為調(diào)用者的前端通信
3)RESTful服務等同于上下文映射中的開放主機服務(OHS)熄阻,或MVC模式的控制器(Controller)斋竞,屬于基礎設施層的組件
3.領域模型產(chǎn)生的依賴
- 以查詢客戶訂單信息為例,可以引入資源庫對象來履行查詢職責秃殉。查詢訂單時坝初,將SaleOrder作為聚合根,對應的SaleOrderRepository作為資源庫放到訂單上下文钾军。在分層架構(gòu)中鳄袍,資源庫對象可能會被封裝到應用服務中,也可能直接暴露給作為適配器的REST服務吏恭,例如
@Path("/saleorder-context/saleorders/{customerId}")
public class SaleOrderController {
@Autowired
private SaleOrderRepository repository;
public List<SaleOrder> allSaleOrdersBy(CustomerId customerId) {
return repository.allSaleOrdersBy(customerId);
}
}
REST服務的調(diào)用者并非客戶上下文拗小,而是前端或客戶端,從而接觸客戶與訂單的包含關系
- 以訂單上下文在查詢訂單時需要獲得訂單對應的商品信息為例樱哼,應該采用遵奉者模式哀九,即在訂單上下文重用商品上下文的領域模型?還是重新在訂單上下文定義屬于自己的與商品有關的領域模型唇礁?
1)選擇前者勾栗,即重用商品領域?qū)ο髸r惨篱,可以提高代碼重用盏筐,在今后的修改中避免散彈式的修改,但是由于兩個限界上下文對商品的需求不同砸讳,重用的商品模型需要同時應對兩種不同的需求琢融,從而主鍵成為一個低內(nèi)聚的對象
2)選擇后者界牡,即分別為商品上下文和訂單上下文建立商品領域?qū)ο螅m然會帶來代碼的重復漾抬,但分離的兩個模型可以獨自應對不同的需求變化宿亡,即"獨立演化"。 - 因此纳令,常常在兩個不同的限界上下文為相同或相似的領域概念分別建立獨立的領域模型挽荠,如下圖所示
4.數(shù)據(jù)產(chǎn)生的依賴
- DDD中,通過領域模型的資源庫訪問數(shù)據(jù)庫平绩,與數(shù)據(jù)庫交互的對象是領域模型對象(實體和值對象)圈匆,即使有依賴,也是領域行為與領域模型導致的
- 有時候出于性能或其他原因考慮捏雌,一個限界上下文訪問屬于另一個限界上下文邊界的數(shù)據(jù)時跃赚,會跳過領域模型而直接通過SQL或存儲過程的方式對多張表執(zhí)行關聯(lián)查詢。這種訪問跨限界上下文數(shù)據(jù)表的方式確實是最簡單最高效的實現(xiàn)方式性湿,但需要限界上下文之間數(shù)據(jù)庫共享
- 這種數(shù)據(jù)依賴值得警惕纬傲,SQL乃至存儲過程形成的數(shù)據(jù)表關聯(lián)難以解耦。一旦系統(tǒng)架構(gòu)需要從單體架構(gòu)(或數(shù)據(jù)庫共享架構(gòu))演進到微服務架構(gòu)肤频,最大的障礙不是代碼層面而是數(shù)據(jù)庫層面的依賴叹括,即大量復雜的SQL與存儲過程
- SQL與存儲過程的問題在于
1)無法為SQL和存儲過程編寫單元測試
2)SQL與存儲過程的可讀性通常較差,難以重用
3)SQL與存儲過程的優(yōu)化策略限制太大