談談微服務架構中的領域驅動設計
https://mp.weixin.qq.com/s/43HSud6ijdVzPA_wdLrxzQ
談談微服務架構中的領域驅動設計
本文是關於領域驅動設計與微服務架構結合的心得體會,通過對整個體系的思考和落地相關各個方面做的梳理,爲大家提供了實踐參考,從而幫助大家使用這套組合拳來應對複雜的大型企業軟件開發。
圖1:微服務之旅
微服務是一種應用架構風格,源於領域驅動設計架構和開發運維一體化,它具有明確的限界上下文,接口和依賴。每個微服務都是一個鬆耦合的遵循單一職責原則的服務,每個組件都是完整而小規模的應用,它關注實現某個單一業務。業務對於最終用戶纔是有意義的——而不是技術或者基礎設施的微服務。每個都有清晰的接口和依賴(如對其它微服務和外部資源)所以微服務可以相對獨立地運行,相應的團隊可以做到獨立開發。
微服務讓開發者(不僅是計算機)更高效。因爲它使開發者能在小型團隊中能完成有意義的工作(例如:開發應用的價值功能)。小型團隊讓開發者(一般來說對所有人)更有生產力,因爲他們開更少的會(以及溝通與協作的其他方面),用更多的時間用來開發程序。最終用戶使用的是程序員開發的軟件,而不是開發者們參加的會議、畫的架構圖或者他們發給經理的狀態報告。越多時間花在編程,就對用戶創造越多價值。(否則,如果開發者寫的代碼不能爲用戶帶來價值,那就錯了!這個團隊就是無效的,或者他們需要思考一下職業規劃!)
在探究微服務設計模式之前,首先,我們要分析和理解業務領域(業務能力概念);當下業務環境已經極其複雜,競爭使得對錯誤的容忍度很小,一點錯誤的決策就會導致災難性的後果。減輕這個風險很關鍵,所以推薦採用一種能應對複雜領域的方法;領域驅動設計是一套關於解決複雜領域模型的軟件開發方法;它的思路是圍繞着業務模型來連接和實現業務核心概念。在領域專家與開發者之間的通用術語是:領域邏輯,子領域,限界上下文,上下文映射,領域模型和通用語言,用它們來協作完善應用模型和解決任何領域相關的主要矛盾。
微服務也不是沒有缺點;缺少DevOps和自動化會導致你的微服務進程難以開展,帶來的痛苦可能多於好處,這是後續博客再談論的話題;目前我們先聚焦在微服務架構的收益,它爲組織帶來不僅業務能力和模塊化的服務,還有:
-
伸縮性
-
可用性
-
彈性
-
獨立,自治性
-
去中心化治理
-
故障隔離
-
自動治理
-
通過DevOps實現持續交付
用合適的方法構建微服務,更多是在討論軟件架構模式,提供一套結構化的方法幫助複雜的領域軟件的設計決策。它關注如何讓某個特殊業務領域的所有知識獲得更好的理解。重要的一點是必須從解決業務理解及真實世界的建模出發。領域驅動設計是一個基於戰略價值的框架,它關心將業務領域概念映射到軟件概念。
微服務實現可以受益於以下的規範方法:
-
領域分析
-
定義定界上下文
-
定義實體,聚合和服務
-
識別微服務
領域分析
一個領域是指真實世界的某方面的解決方案(如:汽車,銀行,抵押,貸款等), 領域是需要開發者實現的系統的需求和驗收標準;領域可能以很高層次的形式隱含業務部分區別。爲了微服務的成功實現,我們需要一個清晰的關注點隔離,應用一些邊界劃分方法,就像領域驅動設計建議的:
-
團隊通常工作於某一個業務領域
-
此業務領域是這個團隊的核心工作
-
領域粒度取決於團隊在組織中的位置
領域模型概念採集和處理,需要在這方面有深度的理解,所以最好的實現方式是採用“事件風暴”;事件風暴是一個基於工作坊的快速弄清領域中內容的方法。這個流程始於一個領域事件上下文,而後將事件視爲一個模型裏的基礎元素。
子領域
一個領域可以被分解爲子領域,每個爲一個領域的特定一方面。典型的代表一些與用戶有通用語言的組織結構。舉例來講,汽車業務可以分解成物流,研發,貿易,生產和市場;而研發領域自身又可以分解爲設計,CAD(電腦輔助設計),測試,輔助系統,娛樂信息,生產計劃,和引擎研發,以上是子領域與領域分解的示例。
-
每個領域可以由子領域構成
-
映射應用到領域與子領域是一個典型的企業架構方法
-
每個子領域可以包含更細分的一級子領域
-
典型的黑白盒建模
-
各個子領域之間可以通信
圖2:領域模型設計
領域模型
領域模型是一個項目覆蓋的業務區域;它有自己的術語,通用語言,需求和要解決的問題;它是這個小世界裏的一個具體領域。這個領域可以是汽車,銀行,抵押,貸款,儲蓄,信用卡,小額貸款,內容管理,以及我們的新項目。一個領域有它的自然邊界,不能包含所有知識。
限界上下文定義
在決定好領域的分解方案後,一個首要任務就是識別限界上下文。當你在明確定義限界上下文時,你經常會發現是否有模型的元素要膨脹到多個上下文。限界上下文是一種適用於領域模型的概念性的邊界。整個的業務模型太大也太複雜,而難以理解也使得維護一個企業的完整的統一的模型變得不可能。我們需要標記模型間的邊界和關係,那麼就要準備開展戰略設計,使用:
-
子領域
-
限界上下文
-
領域模型
-
通用語言
-
上下文映射
限界上下文是一種戰略設計,有着清晰的邊界,裏面包含已存在的領域。在這個邊界內,通用語言的所有術語和習語都有着特定的含義,且模型能精確反映出這些含義。這個邊界也使不同的服務和接口的交互有了正式有效的表達。
通用語言
通用語言是領域驅動設計的一個術語,是在開發者與用戶之間建立一個通用的,嚴謹的語言的實踐。這個語言應該基於軟件開發用到的領域模型——因此這就需要嚴謹,因爲軟件不能處理含糊的東西。
限界上下文實現
限界上下文是一個清晰的邊界圍繞着領域模型,它確定領域的各個部分用以建模。領域模型主要封裝通用語言和它的領域模型,但是它還包含建模存在着什麼樣的交互。限界上下文是一個軟件構件,它被視爲界定一個微服務功能集的神器。
-
一個團隊一個限界上下文
-
每個限界上下文獨立代碼庫
-
領域模型 + 數據庫表結構 + 用戶界面 + 服務API
-
細分大的領域成爲小的上下文
-
每個上下文可以有自己的通用語言和它自己的領域模型
-
限界上下文可能共享領域的一些方面
上下文映射
譯者注:可能譯爲“上下文地圖”更容易理解,很多資料翻譯爲映射,理解起來比較抽象。
企業應用會有多個模型,並且每個模型有它自己的限界上下文。一種明智的做法是用上下文作爲劃分組織結構的依據。因爲一個團隊裏的人溝通更容易,他們可以更好的集成模型和實現。當每個團隊都在自己的模型上工作時,每個人對全貌有個認知都是有益的。
定義實體,聚合和服務
實體,值對象,聚合,領域服務與工廠和倉庫是領域驅動設計的戰術方法的基本要素,如下圖:
圖3:領域驅動設計戰術設計
Eric Evans的書《領域驅動設計:軟件核心複雜性應對之道》描述了這些細節;我只在這裏做個入門。
實體
設計實體這個領域對象,使它關注領域的特性,我們必須正確的確定它的特性以及我們如何獲取它。它是:
-
一個本質特性的名詞
-
可變的——隨時間可變狀態
-
可與其它實體和值對象關聯
-
不能共享
-
實體有歷史記錄可以被追蹤
值對象
我們設計值對象這個領域概念,是當關注領域模型的某個元素的屬性且它不是此模型的特性時,我們應該儘量用值對象建模而不是實體。確定一個概念是否是一個值,應該確定它是否具有以下特徵:
-
它測量量化或者描述領域的一部分
-
它可以被設置爲不可變——狀態不可變
-
它把一個概念建模,將相關屬性組成一個整體的單元
-
當測量值或者描述變更,它可被整體替換
-
它可以與其他同類的值作比較
-
提供給它的合作者以無害的行爲
-
沒有唯一標識的一個名詞
-
不可變的——狀態不可變
-
可與其它實體關聯
-
可以共享
-
沒有生命週期,沒有歷史與之關聯
-
在數據庫中不應該有它的表
聚合
聚合是一組關聯的對象羣,被當成一個單元根實體(也可看成事務)。聚合有清晰的邊界(僅關心聚合內的對象的完整性和職責 ,它不關心外部對象)。聚合保護內部對象不受外部世界改變(外部對象只可以被聚合訪問,不能改變聚合內對象的狀態)。聚合的職責是保護它的實體和值對象的完整性。
-
每個聚合有一個根實體
-
關聯的實體被根引用,但其他實體不行
-
所有操作都是根完成
4個使聚合設計更簡明的規則:
1. 在一致的邊界內建模客觀不變性
-
不變性是業務規則,必須保持一致
-
好的聚合可以做任何修改,而不變性必須一致
-
應用的一個恰當的事務,只修改一個聚合
-
適當的設計聚合:按業務要求可以任何方式修改, 而不可變性要在一個事務內保持完全一致。
2. 設計小的聚合
-
如果設計了大聚合,可能會面對伸縮性能問題
-
限制聚合到根實體,且有少量屬性和/或值對象
-
檢查是否定義的不變性是真實的
3. 用標識引用其它聚合
-
我們要抵禦在一個事務裏修改2個聚合的誘惑
4. 在邊界外使用最終一致性
-
與業務覈實是否最終一致性可以接受。通常是可以的!任何要橫跨聚合的規則,都不能期望它每時每刻保持最新。
服務
領域服務是充實了領域的專有任務的一系列無狀態操作, 它執行有意義的業務處理,且能轉換一個領域對象從一種組合到另一種,通常:
-
服務是你應用的行爲
-
服務導致實體的狀態變化
-
有的操作的概念不能放在任何領域對象裏
-
無狀態
-
接口定義爲領域模型之外的元素
-
服務可以是任何層的一部分(應用,領域,基礎設施)
-
仍然被通用語言和領域專家的命名原則驅動
工廠
工廠模式有職責收集需要的信息來構建領域對象成爲聚合根;工廠模式的最佳實踐:
-
創建實體和值對象
-
只當實體的創建很複雜的時候使用
倉庫
倉庫封裝了存儲在數據庫中的一個對象集合。
-
實體集合
-
關心實體的更新
-
關心獲取已經保存的實體
-
一個倉庫一個聚合根
-
存儲層的實現可以是文件,存儲或者內存數據庫等
識別微服務
第4步(識別微服務)由微服務架構,微服務分層,後端,前端,異步通信和微服務交互組成,它們可以幫助開發微服務應用。
微服務架構
我們爲微服務應用定義了一系列的限界上下文。然後我們進一步審視其中一個限界上下文,調整邊界,並識別一系列的實體,聚合根和領域服務,這樣我們就可以定義清架構,將應用結構化爲一系列鬆耦合,協作良好的服務。每個服務實現了一些有限的,相關的功能集。應用可以由很多服務組成,如訂單管理服務,客戶管理服務等等。
服務交互使用同步協議如HTTP/REST或者異步協議如AMQP。各個服務可以獨立開發和部署。每個服務都有獨立的數據庫,互相之間是解耦的。服務間的數據一致性靠使用Saga模式維護。
服務是很細碎的,客戶端應用通常需要與多個服務交互來匯聚所需的數據。爲了讓服務端的修改不影響客戶端調用,我們可以使用API網關。API網關是一個隱藏所有微服務的抽象層,暴露給客戶端來調用。到達API網關的請求將會被代理或路由到相應的後端服務。網關也可以幫助我們有效的監控服務的使用狀況。
微服務是這個架構中的一個組件:
-
每個都是一個微型的應用
-
每個都關注一個任務,一個業務能力(單一職責原則:每個微服務只實現一個領域限界上下文內的業務職責)。從軟件的角度說,系統需要被拆解成多個組件,每個組件就是一個微服務。爲了實現更小的內存佔用和快速啓動,微服務必須得是輕量的。)
-
每個都可以獨立的部署和更新
-
鬆耦合
-
每個都有設計精良的接口:REST APIs
圖4:微服務架構
微服務架構能提供除了靈活性和彈性以外更多的好處,如:
-
微服務架構是非常有前景的方法,可以以敏捷的方法設計和構建高伸縮性的應用。服務本身也比較直截了當,關注在把某方面的業務做好,所以它會比較容易測試而保障高質量。
-
每個服務可以用合適的技術棧來構建,允許混合持久化技術以及其它類似的技術。你不會受項目的其它部分技術選型的牽制。
-
在這個架構下開發者們可以獨立交付,這對持續交付很友好,允許頻繁發佈的同時保持系統的其它部分穩定。
-
萬一一個服務宕掉,它只會影響直接依賴它的模塊(如果有的話)。其它模塊仍然正常。
-
外部配置:把配置託管到外部的配置服務器,可以爲每個環境維護成層級結構
-
一致性:服務可以按同一樣式編碼,遵從統一的編碼規範和命名規範……
-
有彈性:服務應該處理各方的異常,如技術原因(連接和運行時),和業務原因(非法輸入),而不能崩潰。模式如斷路器和頭信息塊可以幫助隔離和包容錯誤。
-
良好的治理:微服務應該通過JMX API或HTTP API上報它的使用統計,被訪問次數,平均響應時間等。
-
版本控制:微服務可能需要爲不同的客戶端支持多個版本,直到所有的客戶端都遷移到了高版本。應該依據支持的新特性和缺陷修復,有一個清晰的策略……
微服務架構分層
業務服務一般是領域模型和整合層的職責,這點仍然延續(也是SOA的模式)。就是說這些層不會橫跨整個應用,它們只在服務內做好封裝。(類似O/R mapping就會貫穿整個應用被廣泛複用。微服務更重視獨立性多於複用性。並且你可能是在用NoSQL數據庫,所以你不會需要O/R mapping代碼。但是事實上,你的業務可能需要與遺留企業數據庫對接,如果它是基於SQL的,還是需要O/R mapping代碼 )。
應用模型通常作爲門面層:一站式爲客戶端收集所有業務服務,匯聚起來像一個爲客戶端定製的應用。但是現在你要爲每個客戶類型構建一個應用模型,而不是一個單一應用模型貫穿整個應用。
展示層發生了什麼呢?它被移入了客戶端(原本就該聚合在一起!)。客戶端可以是移動APP,它可能是一個展示了應用模型中的一些數據的視圖;或者是一個WEB應用。WEB應用通常會包含很多視圖層的代碼來渲染HTML,且需要在HTTP會話中維持視圖狀態。現代的技術是用HTML5和CSS3的,網頁瀏覽器使用下載到本地的靜態文件來渲染和存儲會話狀態。
圖5:微服務架構分層
用戶體驗適配層(BFF)
每個適配層都是外部前端的一個後端,一個典型的GUI開發團隊都有一對後端和前端開發;在團隊裏使用相同或者兼容的語言。通常,相對於一個團隊編寫後端,其他人實現客戶端,取而代之的是讓同一個團隊負責一對前後端。這對前後端爲彼此的實現而設計,所以溝通是發生在一個團隊內,不是在團隊間。不同的客戶技術各不相同,所以讓團隊專攻某一方面。
圖6:前端的後端
異步通信
異步通信能讓微服務更健壯:
-
當提供者執行時,請求者不必阻塞
-
不同的請求者可以同時處理響應
-
消息系統掌控所有動作和結果
考慮異步集成。同步REST通常更簡單,所以一開始先這麼做。然後考慮也可能由於一些自然的需求(長時間運行,後臺運行)或者讓集成更可靠和健壯而從策略上將一些集成點轉換爲異步的。
怎麼說異步執行更可靠?同步時,整個週期——請求者,提供者,消息——必須在整個執行週期保持正常。而異步,執行過程被拆分成3 - 4部分;如果某一個失敗,系統可能重試。請求者是無狀態的,收到響應的實例甚至不必是發送請求的實例,這就支持了水平擴容帶來高可靠。
圖7:異步通信
微服務交互
不同的微服務可以以不同的語言實現(現在或未來),所以不要鎖定在某個語言的集成技術(如,Java socket,甚至CORBA/IIOP)。時下的標準是REST和JSON/REST,推薦使用。異步集成,遵循開放雲友好的標準,像AMQP(一個開放的標準事務消息協議,比採用商業消息中間件明顯降低成本。在傳輸關鍵業務,或在組織間傳輸實時數據和安全地在虛擬雲計算事務環境中傳輸的理想協議)或者Kafka。不要衝動的使用像XML或者串行化的解決方案。那會導致協議數量大爆炸,每個API提供者與消息者都可能有不同的協議。
其它異步協議的例子有:
-
Message Hub
-
MQ Light
-
RabbitMQ
輕量協議:
-
REST如JSON或者HTTP
-
消息隊列,如Kafka
目標是用這些方法完全解耦:
-
只要有可能就用消息隊列
-
使用服務註冊中心或者服務發現
-
負載均衡
-
斷路器模式
我們可以混合同步和異步。通常請求/響應可以既有同步也有異步,一個單一的調用可以是同步的,否則是異步。
圖8:微服務交互
總結
我們寫這篇文章的目標是分享我們如何合併領域驅動設計與微服務架構的;DDD是圍繞解決領域模型的複雜性的軟件開發方法;這個方法反覆推敲如何將實現與核心領域概念連接爲業務模型。通用術語在業務/領域專家和開發團隊之間有領域邏輯,子領域,限界上下文,上下文映射,領域模型,和通用語言用來協同和優化應用模型和解決領域相關問題。我們的方法是結構化理解業務領域使用領域分析,定義限界上下文,定義實體,聚合根和服務,最終識別出微服務。微服務能提供傳統架構不具備的獨特優勢,它提供擴展性,可用性,可靠性,並且每個微服務是鬆耦合單一職責的。
原文鏈接:https://medium.com/design-and-tech-co/implementing-domain-driven-design-for-microservice-architecture-26eb0333d72e