如何構建基於 DDD 領域驅動的微服務?

儘管微服務中的 “微” 一詞表示服務的規模,但它並不是使用微服務的唯一標準。當團隊轉向基於微服務的架構時,他們旨在提高敏捷性以及自主且頻繁地部署功能。很難確定這種架構風格的簡單定義。我喜歡 Adrian Cockcroft 的關於微服務的簡短定義:“面向服務的體系結構,它由鬆散耦合的、具有上下文邊界的元素組成。”

儘管這定義了高級設計啓發式技術,但微服務架構具有一些獨特的特性,使其有別於以往的面向服務的架構。以下是其中一些特徵。這些以及其他一些文檔都有據可查 - Martin Fowler 的文章和 Sam Newman 的 Building Microservices,僅舉幾例。

  1. 服務具有圍繞業務上下文而不是任意技術上抽象的明確定義的邊界

  2. 通過意圖公開界面隱藏實現細節並公開功能

  3. 服務不會共享超出其邊界的內部結構。例如,不共享數據庫。

  4. 服務可以抵抗故障。

  5. 團隊獨立擁有職能,並能夠自主發佈變更

  6. 團隊擁護自動化文化。例如,自動化測試,持續集成和持續交付

簡而言之,我們可以將這種體系結構樣式總結如下:

松耦合的面向服務的體系結構,其中每個服務都包含在定義明確的有界上下文中,從而可以快速,頻繁且可靠地交付應用程序。

領域驅動設計和有界上下文

微服務的力量來自明確定義其職責並劃分它們之間的邊界。此處的目的是在邊界內建立高凝聚力,並在邊界外建立低耦合(banq 注:高凝聚低耦合)。也就是說,趨於一起改變的事物應該屬於同一事物。就像在許多現實生活中的問題一樣,這說起來容易做起來難,業務在不斷髮展,邏輯的假設前提也會隨之改變。因此,重構的能力是設計系統時要考慮的另一關鍵問題。

領域驅動設計(DDD)是關鍵,在我們看來,這是設計微服務時必不可少的工具,無論是打破整體還是實施未開發項目。領域驅動的設計(Eric Evans 在他的書中提出)是一組思想、原理和模式,可幫助基於業務領域的基礎模型設計軟件系統。開發人員和領域專家共同合作,以通用的通用語言創建業務模型。然後,他們將這些模型綁定到有意義的系統,並在這些系統與從事這些服務的團隊之間建立協作協議。更重要的是,他們設計了系統之間的概念輪廓或邊界。

微服務設計從這些概念中汲取了靈感,因爲所有這些原理都有助於構建可以相互獨立變化和發展的模塊化系統。

在繼續進行之前,讓我們快速瞭解一下 DDD 的一些基本術語。域驅動設計的完整概述超出了本博客的範圍。我們強烈建議任何嘗試構建微服務的人推薦 Eric Evans 的書籍。

領域:代表組織的工作。例如它是零售或電子商務。

子域:組織或組織內的業務部門。域由多個子域組成。

無所不在的語言:這是用於表達模型的語言。在下面的示例中,Item 是一個模型,屬於這些子域中每個子域的通用語言。開發人員,產品經理,領域專家和業務涉衆都同意使用相同的語言,並在其工件(代碼,產品文檔等)中使用該語言。

有界上下文:域驅動的設計將有界上下文定義爲 “單詞或語句能確定其含義的設置”。簡而言之,這意味着模型是有意義的邊界。在上面的示例中,“項目” 在每種上下文中的含義不同。在目錄上下文中,項目表示可售產品,而在購物車上下文中,則表示客戶已將其添加到購物車中的項目。在 “運輸” 上下文中,它表示將要運送給客戶的倉庫物料。這些模型中的每一個都是不同的,並且每個都有不同的含義,並且可能包含不同的屬性。通過將這些模型分離並隔離在它們各自的邊界內,我們可以自由地表達模型而沒有歧義。

注意:必須瞭解子域和有界上下文之間的區別。子域屬於問題空間,即您的企業如何看待問題,而受限上下文屬於解決方案空間,即我們將如何實施問題的解決方案。從理論上講,每個子域可能具有多個有界上下文,儘管我們努力爲每個子域提供一個有界上下文。

微服務與有限上下文如何相關

現在,微服務在哪裏適合?可以說每個有界上下文都映射到微服務嗎?是的,我們將明白爲什麼。在某些情況下,有界上下文的邊界或輪廓可能很大。

考慮上面的例子。定價綁定上下文具有三個不同的模型 - 價格,定價項目和折扣,每個模型負責目錄項目的價格,計算項目列表的總價格並分別應用折扣。我們可以創建一個包含以上所有模型的系統,但是它可能會成爲一個不合理的大型應用程序。如前所述,每個數據模型都有其不變性和業務規則。隨着時間的流逝,如果我們不小心的話,系統可能會變成一個泥濘的大球,邊界模糊,職責重疊,甚至可能回到我們開始的地方—一個整體。

對系統進行建模的另一種方法是將相關模型分離或分組爲單獨的微服務。在 DDD 中,這些模型(價格,定價項目和折扣)被稱爲聚合 Aggregates。聚合是組成相關模型的獨立模型。您只能通過已發佈的界面更改聚合的狀態,並且聚合可確保一致性,並且不變量保持良好狀態。

聚合是關聯對象的集羣,被視爲數據更改的單元。外部引用僅限於 AGGREGATE 的一個成員,稱爲根。一組一致性規則適用於 AGGREGATE 的邊界。推薦一個 Spring Boot 基礎教程及實戰示例:https://www.javastack.cn/categories/Spring-Boot/

同樣,沒有必要一定要將每個聚合建模爲一個獨特的微服務。圖中的服務(聚合)是一對一關係,但這不一定是規則。在某些情況下,在單個服務中託管多個聚合可能是有意義的,尤其是當我們不完全瞭解業務領域時。需要注意的重要一點是,只能在單個聚合中保證一致性,並且只能通過已發佈的界面修改聚合。任何違反這些規定的行爲都有變成大泥球的風險。

上下文映射—精確劃分微服務邊界的一種方法

整體結構通常由不同的模型組成,大多數模型是緊密耦合的 - 模型可能知道彼此的親密細節,更改一個模型可能會對另一個模型產生副作用,依此類推。分解整體時,確定這些模型(在這種情況下爲集合)及其關係至關重要。上下文映射可以幫助我們做到這一點。它們用於標識和定義各種有界上下文和聚合之間的關係。在上面的示例中,有界上下文定義了模型的邊界(價格,折扣等),而上下文映射定義了這些模型之間以及不同有界上下文之間的關係。

上下文映射的完整探索不在本博客的討論範圍之內,但我們將通過一個示例進行說明。下圖顯示了處理電子商務訂單付款的各種應用程序。

  1. 購物車上下文負責訂單的在線授權;訂單上下文流程過帳付款後的付款流程;聯絡中心會處理所有例外情況,例如重試付款和更改用於訂單的付款方式

  2. 爲了簡單起見,讓我們假設所有這些上下文都是作爲單獨的服務實現的

  3. 所有這些上下文封裝了相同的模型。

  4. 請注意,這些模型在邏輯上是相同的。也就是說,它們都遵循相同的通用域語言 - 付款方式,授權和結算。只是它們是不同上下文的一部分。

重新定義服務邊界—將聚合映射到正確的上下文

錯誤案例如下圖:

電子商務中所有模型都直接與單個支付聚合的網關上下文(payment gateway context)集成,支付需要保證事務性,但是由於與多個服務集成,支付的事務性就不能通過在各種服務之間強制執行不變性和一致性來實現,(banq 注:當然有人就提出分佈式事務的概念來在這些不同服務之間實現支付過程的事務性,這其實是在錯誤設計基礎上的僞概念)。

另外,分佈式事務系列面試題和答案全部整理好了,微信搜索 Java 技術棧,在後臺發送:面試,可以在線閱讀。

請注意,支付網關中的任何更改都將迫使更改多個服務,並可能更改多個團隊,因爲不同的組可以擁有這些上下文。

進行一些調整並使聚合與正確的上下文對齊,我們可以更好地表示這些子域如下圖。發生了很多變化。讓我們回顧一下更改:

通常,整體 (單體) 或遺留應用程序具有許多聚合,通常具有重疊的邊界。創建這些聚合及其依賴關係的上下文地圖有助於我們瞭解將要從這些整體中擺脫出來的任何新微服務的輪廓。請記住,微服務架構的成功或失敗取決於聚合之間的低耦合以及這些聚合之間的高內聚性。

同樣重要的是要注意,有界上下文本身就是合適的內聚單元。即使上下文具有多個聚合,整個上下文及其聚合也可以組成一個微服務。我們發現這種啓發式方法對於有些晦澀的領域特別有用 - 考慮組織正在涉足的新業務領域。您可能對分離的正確邊界沒有足夠的瞭解,並且聚集體的任何過早分解都可能導致昂貴的重構。想象一下,由於數據遷移,不得不將兩個數據庫合併爲一個,因爲我們偶然發現兩個聚合屬於同一類。但是請確保通過接口將這些聚合充分隔離,以使它們不知道彼此的複雜細節。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/kYUXUzxPNP51WLf0uKWlrw