如何使用 DDD 指導微服務拆分?
本文轉載自微信公眾號「架構精進之路」,作者架構精進之路。轉載本文請聯系架構精進之路公眾號。
軟件架構發展經歷
軟件架構的發展經歷了從單體架構、垂直架構、SOA架構到微服務架構以及到現在最新的service mesh(網格服務架構)的過程。借用dubbo的網站架構發展圖和說明:
微服務存在的問題
進入微服務之后 , 解決了集中式架構的單體應用很多問題, 但是新的問題應運而生 , 微服務的力度應該多大 ?微服務如何設計呢?微服務如何拆分 ?微服務邊界在哪里 ?
很長時間人們都沒有解決這一問題,就連Martin Fowler在提出微服務架構的時候也沒有告訴我們這該如何拆分微服務。
甚至在很長的時間里人們對微服務拆分產生了一些誤解, 有人認為:"微服務很簡單,就是將之前的單體應用拆分成多個部署包, 或者將原來的單體應用架構替換為一套支持微服務的技術架構,就算是微服務了。" 還有人認為微服務應該拆分得越小越好。
鑒于上述情形, 很多項目因為前期拆分過度, 導致復雜度過高, 導致后期難以運維甚至難以上線。
可以得出一個結論:微服務拆分困境產生的根本原因就是不知道業務或者微服務的邊界到底在什么地方。換句話說,確定了業務邊界和應用邊界,這個困境也就迎刃而解了。
DDD的誕生
而DDD就是解決了這個確定業務邊界的問題,可見DDD并不是一種技術架構,而是一種劃分業務領域范圍的方法論。DDD的興起是由于很多熟悉領域驅動建模(DDD)的工程師在進行微服務設計時, 發現用DDD的思路進行業務梳理可以很好規劃服務邊界, 可以很好實現微服務內部和外部的"高內聚、低耦合"。于是越來越多的人將DDD作為業務劃分的指導思想。
DDD是一種拆解業務、劃分業務、確定業務邊界的方法, 是一種高度復雜的領域設計思想,將我們的問題拆分成一個個地域, 試圖分離技術實現的復雜性,主要解決的是軟件難以理解難以演進的問題,DDD不是一種架構, 而是一種架構方法論, 目的就是將復雜問題領域簡單化, 幫助我們設計出清晰的領域和邊界, 可以很好的實現技術架構的演進。DDD包括兩部分,戰略設計部分和戰術設計部分。
- 戰略設計主要從業務視角出發,建立業務領域模型,劃分領域邊界,建立通用語言的限界上下文,限界上下文可以作為微服務設計的參考邊界。
- 戰術設計則從技術視角出發,側重于領域模型的技術實現,完成軟件開發和落地,包括:聚合根、實體、值對象、領域服務、應用服務和資源庫等代碼邏輯的設計和實現。
微服務拆分難題
開發者在剛開始嘗試實現自己的微服務架構時,往往會產生一系列問題 :
- 微服務到底應該怎么劃分?
- 一個典型的微服務到底應該有多微?
- 如果做了微服務設計,最后真的會有好處嗎?
回答上面的問題需要首先了解微服務設計的邏輯,科學的架構設計應該通過一些輸入并逐步推導出結果,架構師要避免憑空設計和“拍腦門”的做法。
服務的劃分有一些基本的方法和原則,通過這些方法能讓微服務劃分更有操作性。最終在微服務落地實施時也能按圖索驥,無論是對遺留系統改造還是全新系統的架構都能游刃有余。
微服務拆分的幾個階段
在開始劃分微服務之前,架構師需要在大腦中有一個重要的認識:微服務只是手段,不是目的。
微服務架構是為了讓系統變得更容易拓展、更富有彈性。在把單體應用變成靠譜的微服務架構之前,單體系統的各個模塊應該是合理、清晰地。
也就是說,從邏輯上單體系統和微服務沒有區別,某種理想情況下微服務只是把單體系統的各個模塊分開部署了而已
大量的實踐教訓告訴我們,混沌的微服務架構,比解耦良好的單體應用會帶來更多麻煩。
混亂的微服務VS良好的單體
領域驅動設計立足于面向對象思想,從業務出發,通過領域模型的方式反映系統的抽象,從而得到合理的服務劃分。
采用 DDD 來進行業務建模和服務拆分時,可以參考下面幾個階段:
- 使用 DDD(領域驅動建模) 進行業務建模,從業務中獲取抽象的模型(例如訂單、用戶),根據模型的關系進行劃分限界上下文。
- 檢驗模型是否得到合適的的抽象,并能反映系統設計和響應業務變化。
- 從 DDD 的限界上下文往微服務轉化,并得到系統架構、API列表、集成方式等產出。
使用DDD劃分微服務的過程
如何抽象?
抽象需要找到看似無關事務的內在聯系,對微服務的設計尤為重要。
然而現實的例子比比皆是,電信或移動營業廳還需要用戶分兩步辦理號卡業務、寬帶業務。原始是不合適的抽象模型造成的,并最終影響了微服務的劃分。
我們可以使用概念圖來描述一些概念的抽象關系。
商品這一概念的概念圖
如果沒有抽象出領域模型,就得不到正確的微服務劃分。
使用DDD進行業務建模
通過利用DDD對系統從業務的角度分析,對系統進行抽象后,得到內聚更高的業務模型集合,在DDD中一組概念接近、高度內聚并能找到清晰的邊界的業務模型被稱作限界上下文(Bounded Context)。
限界上下文可以視為邏輯上的微服務,或者單體應用中的一個組件。
在電商領域就是訂單、商品以及支付等幾個在電商領域最為常見的概念;在社交領域就是用戶、群組、消息等。
DDD的方法論中是如何找到子系統的邊界的呢?
其中一項實踐叫做事件風暴工作坊,工作坊要求業務需求提出者和技術實施者協作完成領域建模。把系統狀態做出改變的事件作為關鍵點,從系統事件的角度觸發,提取能反應系統運作的業務模型。再進一步識別模型之間的關系,劃分出限界上下文,可以看做邏輯上的微服務。
事件是系統數據流中的關鍵點,類似于電影制作中的關鍵幀。
例如系統管理員可以登錄、創建商品、上架商品,對應的系統狀態的改變是用戶已登錄、商品已創建、商品已經上架;相應的顧客可以登錄、創建訂單、支付,對應的系統狀態改變是用戶已登錄、訂單已創建、訂單已支付。
利用事件刺探業務黑盒并抽象出模型
在得到模型之后,通過分析模型之間的關系得出限界上下文。例如商品屬性和商品相對于用戶、用戶組關系更為密切,通過這些關系作出限界上下文拆分的基本線索。
其次是識別模型中的二義性,讓限界上下文劃分更為準確。
例如,在電商領域,另外一個不恰當設計的例子是:把訂單中的訂單項當做和商品同樣的概念劃分到了商品服務
但訂單中的商品實際上和商品庫中的商品不是同一個概念。當訂單需要修改訂單下的商品信息時,需要訪問商品服務,這勢必造成了訂單和商品服務的耦合。
合理的設計應該是:商品服務提供商品的信息給訂單服務,但是訂單服務沒有理由修改商品信息,而是訪問作為商品快照的訂單項。
訂單項應該作為一個獨立的概念被劃分到訂單服務中,而不是和商品使用同一個概念,甚至共享同一張數據庫表。
典型具有”二義性“陷阱的場景
一組關系密切的模型形成了上下文(context),二義性的識別能幫我們找到上下文的邊界(bounded)。
驗證和評審領域模型
前面我們說到限界上下文可以作為邏輯上的微服務,但并不意味著我們可以直接把限界上下文變成微服務。
限界上下文被設計出來后,驗證它的方法可以從我們采用微服務的兩個目的出發:降低耦合、容易擴展,可以作為限界上下文評審原則:
原則1:設計出來的限界上下文之間的互相依賴應該越少越好,依賴的上游不應該知道下游的信息。
原則2:使用潛在業務進行適配,如果能在一定程度上響應業務變化,則證明用它指導出來的微服務可以在相當一段時間內足以支撐應用開發。
但是理想的領域模型往往抽象程度、成本、復用性這幾個因素中獲取平衡。
”抽象”的成本
用一個簡單的圖來表達話,我們的領域模型設計往往在復用性和成本取得平衡的中間區域才有實用價值。
幾個典型的誤區
在大量使用DDD指導微服務拆分的實踐后,我們發現很多系統設計存在一些常見的誤區
主要分為兩類:未成功做出抽象、抽象程度過高、錯誤的抽象。
1)未成功做出抽象
在實際開發過程中,大家都有一個體會,設計階段只考慮了一些常見的服務,但是發現項目中有大量可以重用的邏輯,并應該做成單獨服務。
當我們在做服務拆分時,遺漏了服務的結果是有一些業務邏輯被分散到各個服務中,并不斷重復。
2)抽象程度過高
抽象程度過高最典型的一個特征是得到的限界上下文極端的微小。
抽象程度過高帶來的成本有:更多的微服務部署帶來的運維壓力、開發調試難度提高、服務間通信帶來的性能開銷、跨服務的分布式事務協調等。因此抽象不是越高越好,應根據實際業務需要和成本考慮。
那相應的,微服務到底應該多小呢?
業界流傳一句話來形容,微服務應該多小:“一個微服務應該可以在二周內完成重寫“。
這句話可能只是一句調侃,如果真的作為微服務應該多微的標準是不可取的。
微服務的大小應該取決于劃分限界上下文時各個限界上下文內聚程度。
3)錯誤抽象
對微服務或DDD理解不夠。模型具有二義性,被放到不同的限界上下文。
例如,訂單中的收貨地址、用戶配置的常用地址以及地址庫中的標準地址。
這三種地址雖然名稱類似,但是在概念上完全不是一回事
假如架構師將”地址“劃分到了標準地址庫中,勢必會造成用戶上下文和系統配置上下文、訂單上下文存在不必要的耦合。
抽象錯誤帶來的依賴
上圖的右邊為正常的依賴關系,左邊產生了不正常的依賴,會進一步產生雙向依賴。
從限界上下文到系統架構
在通過 DDD 得到領域模型和限界上下文后,理論上我們已經得到了微服務的拆分。但是,限界上下文到系統架構還需要完成下面幾件事。
1)設計微服務之間的依賴關系
一個合理的分布式系統,系統之間的依賴應該是非常清晰地。依賴,在軟件開發中指的是一個應用或者組件需要另外一個組件提供必要的功能才能正常工作。因此被依賴的組件是不知道依賴它的應用的,換句話說,被調用者不需要知道調用方的信息,否則這不是一個合理的依賴
在微服務設計時,如果 domain service 需要通過一個 from 參數,根據不同的渠道做出不同的行為,這對系統的拓展是致命的。例如,用戶服務對于訪問他的來源不應該知曉;用戶服務應該對訂單、商品、物流等訪問者提供無差別的服務。
因此,微服務的依賴關系可以總結為:上游系統不需要知道下游系統信息,否則請重新審視系統架構。
2)設計微服務間集成方式
拆分微服務是為了更好的集成到一起,對于后續落地來說,還有服務集成這一重要的階段。
微服務之間的集成方式會受到很多因素的制約,前面在討論微服務到底有多微的時候就順便提到了集成會帶來成本,處于不同的目的可以采用不同的集成方式。
- 采用 RPC(遠程調用) 的方式集成。
使用RPC的方式可以讓開發者非常容易的切換到分布式系統開發中來,但是RPC的耦合性依然很高,同時需要對RPC平臺依賴。業界優秀的RPC框架有dubbo、Grpc、thrift等
- 采用消息的方式集成。
使用消息的方式異步傳輸數據,服務之間使用發布-訂閱的方式交互。另外一種思想是通過對系統事件傳遞,因此產生了 Event Sourcing 這種集成模式,讓微服務具備天然的彈性。
- 采用RESTful方式集成。
RESTful是一種最大化利用HTTP協議的API設計方式,服務之間通過HTTP API集成。這種方式讓耦合變得極低,甚至稍作修改就可以暴露給外部系統使用。
這三種集成方式耦合程度由高到低,適用于不同的場景,需要根據實際情況選擇,甚至在系統中可能同時存在。
服務間集成的方式還有其他方式,一般來說,上面三種微服務集成的方式可以概括目前常見系統大部分需求。
總結
這篇文章主要研討了DDD火起來的原因, 解決了什么業界難題, 知道DDD主要思路 , 以及DDD大概的實現步驟等 。
邏輯往往比經驗更為重要。寫這篇文章的初衷是為了得到微服務劃分的依據是什么,我該怎么有說服力的回復?
是具體情況具體分析?By experience?還是說,我是通過一套方法對業務邏輯進行分析得到的。
當沒有足夠的經驗直接解決問題,或問題龐大到不足以使用經驗解決時,能支撐你做出決策就只有對輸入問題進行有效的分析。
使用 DDD 指導微服務劃分,能在一定程度上彌補經驗的不足,做出有理有據的系統架構設計。