限界上下文對 領域驅動架構 有直接到影響
識別限界上下文 與 上下文映射 都是重要過程
限界上下文
是 邏輯架構 以及 物理架構的 參考模型
上下文映射
體現了系統架構的通信模型
限界上下文不僅僅作用於領域層 以及 應用層
是架構設計 而非僅僅領域設計的關鍵因素
限界上下文 是一個 垂直的 架構邊界
針對後端架構層次的垂直拆分
例如:
訂單上下文的內部就包含了應用層、領域層和基礎設施層,每一層的模塊都是面向業務進行劃分,甚至可能是一一對應的。
依賴的技術組件以及選型 屬於架構設計 但不屬於 限界上下文考慮內容
基礎設施層需要訪問的外部資源,以及爲了訪問它需要重用的框架或平臺,與技術決策和選型息息相關,仍然屬於架構設計的考量範圍,但它們不屬於限界上下文的代碼模型
如
NoSQL 數據庫還是關係數據庫
消息隊列是採用 Pull 模式還是 Push 模式。
技術選型上,我們需要確定具體是哪一種數據庫和消息隊列中間件,同時還需要確定訪問它們的框架。
限界上下文的通信邊界
進程邊界隔離(兩個限界上下文屬於不同的進程 比如 運力 以及 人員)
進程內以及進程間存在不同的處理方式:
-
通信
-
消息的序列化
-
資源管理
-
事務與一致性處理
-
部署
通信邊界的不同 影響了 系統對各個組件的 重用方式 以及 共享方式
1 進程內的通信邊界
限界上下文通信方式 爲進程內通信
運行時多個上下文模型運行在同一個進程中
通過 實例化的方式 重用 其他上下文中的功能
Java中 限界上下文模型存在 兩種設計方式:
1 命名空間級別
所有上下文 都處於同一模塊中 通過命名空間(分包)對各個上下文進行隔離
2 模塊級別
不同上下文屬於同一工程的不同模塊 module
越容易重用 越容易產生耦合
對於同一進程的邊界,編寫代碼時要格外遵守 各個上下文的邏輯邊界,確定限界上下文各自對外公開的接口,避免上下文之間產生過多的依賴
如何避免上下文之間產生過多的依賴?
每個上下文有自己的防腐層,用來 減少 外部上下文變化帶來的影響
防腐層 對進程內的多個上下文進行隔離
進程內通信 屬於單體架構
不能針對某一個限界上下文進行水平伸縮
儘管通過分module或 分包 我們守住了模型的邊界 但是各個上下文間是相互耦合的 各個限界上下文間相互影響 團隊內協調成本增加
第2種方式 :上下文處於不同的module的解耦會更加徹底,倘若
限界上下文的劃分足夠合理,也能提高它們對變化的應對能力。
例如,當限界上下文 A 的業務場景發生變更時,我們可以只修改和重編譯限界上下文 A 對應的 Jar 包,其餘 Jar 包並不會受到影響。
多個上下文處於同一進程 由於它們都運行在同一個 Java 虛擬機中,意味着當變化發生時,整個系統需要重新啓動和運行。
2 進程間的通信邊界
多個限界上下文不在同一個進程裏 限界上下文間的通信是跨進程的,不能直接調用另一個限界上下文中的方法 RPC通信
對於進程間 通信 需要考慮 上下文依賴的 外部資源 形成兩種不同風格的架構:
1 數據庫共享架構
2 零共享架構
1 數據庫共享架構
考慮上下文劃分時, 分開考慮代碼模型 以及 數據庫模型 -> 多個被進程分離的上下文 共享一個數據庫 進程分離 數據庫共享
由於未進行分庫 數據庫層面 更容易保證事物的 ACID屬性 可以看做是對 一致性約束的妥協
共享數據庫帶來的問題:
數據庫變化的方向與業務的變化方向不一致,體現:
1 耦合:限界上下文的代碼模型是解耦的, 但是 數據庫層面 存在強耦合的關係
上下文對應的庫表 受到 其他上下文 對應庫表的影響
2 水平伸縮: 實現 某上下文的 應用服務器 可以進行水平 擴展( 不受到其他上下文對應的應用服務器影響) 但是 某上下文 對應的數據庫表 無法做到 獨立於其他上下文的庫表 實現水平擴展
Netflix 提出微服務最佳實踐:每個微服務的數據單獨存儲
但服務的分離 不絕對代表 數據應該分離
原因:對數據進行分庫設計時, 如果僅僅站在業務邊界角度思考 導致分庫粒度太小 帶來不必要的跨庫關聯
建議:將“數據庫共享”模式視爲一種過渡方案,採用演進式的設計。
爲 便於 在 演進設計中 將分表 重構爲 分庫 應該 避免 在兩個限界上下文的表之間 建立 外鍵約束關係(或其他關聯關係 比如 userorg表(當然user 與org 屬於同一上下文))
兩個分處不同限界上下文的服務需要操作同一張數據表(這張表被稱之爲“共享表”)時,就傳遞了一個信號,即我們的設計可能出現了錯誤:
-
遺漏了一個限界上下文,共享表對應的是一個被重用的服務:買家在查詢商品時,商品服務會查詢價格表中的當前價格,而在提交訂單時,訂單服務也會查詢價格表中的價格,計算當前的訂單總額;共享價格數據的原因是我們遺漏了價格上下文,通過引入價格服務就可以解除這種不必要的數據共享。
-
職責分配出現了問題,操作共享表的職責應該分配給已有的服務:輿情服務與危機服務都需要1 從郵件模板表中獲取模板數據,然後2 再調用郵件服務組合模板的內容發送郵件;實際上從郵件模板表獲取模板數據的職責應該分配給已有的郵件服務。
-
共享表對應兩個限界上下文的不同概念:倉儲上下文與訂單上下文都需要訪問共享的產品表,但實際上這兩個上下文需要的產品信息並不相同,應該按照限界上下文的邊界分開爲各自關心的產品信息建表(兩個上下文 各自建立 自己關心 的 xx信息表)。
產生以上錯誤 的根本原因: 沒有通過業務建模,而是在數據庫層面隱式地進行建模
2 零共享架構
兩兩限界上下文共享的外部資源徹底斬斷
涼涼限界上下文間 通過 http接口 或者 tcp接口 實現數據獲取
架構的表現形式爲:
每個限界上下文都有自己的代碼庫、數據存儲以及開發團隊,每個限界上下文選擇的技術棧和語言平臺也可以不同,限界上下文之間僅僅通過限定的通信協議和數據格式進行通信。
複雜度:
1限界上下文之間的通信是跨進程的,我們需要考慮通信的健壯性。
2數據庫是完全分離的,當需要關聯之間的數據時,需得跨限界上下文去訪問,無法享受數據庫自身提供的關聯福利。
3由於每個限界上下文都是分佈式的,如何保證數據的一致性也是一件棘手的問題。
當整個系統都被分解成一個個可以獨立部署的限界上下文時,運維與監控的複雜度也隨之而劇增。
不同進程間通信協議的確定:
1 基於 HTTP 的 REST 服務,也可以
2 通過 RPC 訪問遠程對象,又或者
3 利用消息中間件傳遞消息
是一種分佈式調用,自然存在分佈式系統與身俱來的缺陷: 如網絡不可靠 數據不一致
針對下游服務可能不可用的情況:考慮服務調用的熔斷來及時應對故障,避免因單一故障點帶來整個微服務架構的連鎖反應(不能因爲下游依賴的某一個服務 不可用 導致本服務不可用 及時熔斷)
數據一致性問題:
業務上是否要求嚴格的一致性?
最終一致性(BASE): 可靠事件模式、補償模式、TCC模式 等