日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

如何基于 DDD 构建微服务?

發布時間:2023/12/4 编程问答 50 豆豆
生活随笔 收集整理的這篇文章主要介紹了 如何基于 DDD 构建微服务? 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

本文將討論微服務與 DDD 涉及到的概念、策劃和設計方法,并且嘗試將一個單體應用拆分成多個基于 DDD 的微服務。

微服務的定義

微服務中的“微”雖然表示服務的規模,但它并不是使應用程序成為微服務的唯一標準。當團隊轉向基于微服務的架構時,他們的目標是提高敏捷性,即自主且頻繁地部署功能。

因此,很難給微服務架構風格下一個簡單的定義。我喜歡 Adrian Cockcroft 關于微服務的簡短定義:“面向服務的架構由具有界限上下文、松散耦合的元素組成。”

盡管這定義了一種高級的設計啟發式方法,但微服務架構具有的特性,使其有別于以往的面向服務架構。根據以往的文章,我們總結了微服務架構應具備的一些特征:

  • 服務以業務上下文為中心定義了良好的邊界,而不是以任意的技術抽象為中心;

  • 隱藏實現細節,并通過意圖接口暴露功能;

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

  • 服務具有故障快速恢復能力;

  • 團隊職能獨立,能夠自主發布變更;

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

  • 簡而言之,我們可以將這種架構風格總結如下:

    松散耦合的面向服務的架構,其中每個服務都封裝在定義良好的界限上下文中,支持應用程序快速、頻繁且可靠的交付。

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

    微服務的強大之處在于清晰地定義了它們的職責并劃定了它們之間的邊界。它的目的是在邊界內建立高內聚,在邊界外建立低耦合。也就是說,傾向于一起改變的事物應該放在一起。正如現實生活中的許多問題一樣,但這說起來容易做起來難,業務在不斷發展,設想也隨之改變。因此,重構能力是設計系統時考慮的另一項關鍵問題。

    在我們看來,領域驅動設計 (DDD) 是關鍵,它是設計微服務時必不可少的工具,無論是對單體應用進行拆分還是從頭開始構建一個新項目。領域驅動設計因 Eric Evans 的著作而出名,它是一組思想、原則和模式,可以幫助我們基于業務領域的底層模型設計軟件系統。開發人員和領域專家一起使用統一的通用語言創建業務模型。然后將這些模型綁定到有意義的系統上,在這些系統和處理這些服務的團隊之間建立協作協議。更重要的是,它們設計了系統之間的概念輪廓或邊界。

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

    在繼續深入之前,讓我們快速瀏覽一下 DDD 的一些基本術語。對領域驅動設計的完整概述不在本文的討論范圍之內。

    領域(Domain): 代表組織所做的工作。例如零售或電子商務。

    子域(Subdomain): 組織或組織內的業務部門。一個領域由多個子域組成。

    統一語言(Ubiquitous language):這是用于表達模型的語言。在下面的例子中(圖 1),Item 是一個模型,它是每個子域的統一語言。開發人員、產品經理、領域專家和業務各涉眾方都能就使用這種語言達成一致,并在他們的工件(代碼、產品文檔等)中使用該語言。

    圖 1:電子商務領域中的子域和界限上下文

    界限上下文(Bounded Contexts):領域驅動設計將界限上下文定義為“一個單詞或語句出現時確定其含義的設置”。簡而言之,這意味著模型在邊界內是有含義的。在上面的例子中(圖 1),“Item”在每個上下文中都有不同的含義。在 Catalog 上下文中,Item 表示可出售的產品,而在 Cart 上下文中,它表示客戶已添加到購物車中的商品選項。在 Fulfillment 上下文中,它表示將要運送給客戶的倉庫物料。這些模型各不相同,每個模型都有不同的含義,并且可能包含不同的屬性。通過將這些模型分離并將其隔離在各自的邊界內,我們就可以自由地表達這些模型,而不會產生歧義。

    注意: 必須理解子域和界限上下文之間的區別。子域屬于問題空間,即我們的業務要如何看待問題,而界限上下文屬于解決方案空間,即我們將如何實施問題的解決方案。理論上,每個子域可能有多個界限上下文,盡管我們努力每個子域只提供一個界限上下文。

    微服務和界限上下文如何關聯

    現在,微服務適用于哪些地方?每個界限上下文都能映射到對應的微服務嗎?不一定。我們來看看原因。在某些情況下,界限上下文的邊界或輪廓可能會非常大。

    圖 2:界限上下文和微服務

    考慮上面的例子。定價界限上下文有三個不同的模型:價格(Price)、定價項(Priced items) 和折扣(Discounts),分別負責目錄項的價格、計算列表項的總價以及各自使用的折扣。我們可以創建一個包含上述所有模型的單一系統,但它可能是一個不合理的大型應用程序。如前所述,每個數據模型都有其不變性和業務規則。隨著時間的推移,如果我們不小心的話,這個系統就可能會變成一個大泥球,界限模糊,職責重疊,甚至很可能會回到我們開始的地方——單體應用。

    對這個系統建模的另一種方法是將相關模型分離或分組到單獨的微服務中。在 DDD 中,這些模型(價格、定價項和折扣)被稱為聚合(Aggregates)。聚合是由相關模型組成的自包含模型。我們只能通過已發布的接口來變更聚合的狀態,并且聚合可以確保一致性,而且不變量可以始終保持良好狀態。

    在形式上,聚合是關聯對象的集群,被視為數據變更的單元。外部引用僅限于指定聚合的一個成員,即聚合根。在聚合的邊界內需應用一組一致性規則。

    圖 3:定價上下文中的微服務

    同樣,沒有必要將每個聚合都建模為一個不同的微服務。事實證明,圖 3 中的服務(聚合)就是如此,但這不一定是一個規則。在某些情況下,在單個服務中托管多個聚合可能是有意義的,特別是在我們不完全了解業務領域的情況下。需要注意的一點是,一致性只在單個聚合中才能得到保證,并且聚合只能通過已發布的接口進行修改。任何違反這些規則的行為都有增加應用程序變成一個大泥球的風險。

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

    另一個基本工具是上下文映射,同樣,它也是來自領域驅動設計。一個單體應用通常由不同的模型組成,這些模型大多是緊密耦合的,模型之間可能知道彼此的實現細節,變更一個模型可能造成另一個模型的副作用等等。當你分解單體應用時,確定這些模型(在這里是聚合)及其關系是至關重要的。上下文映射可以幫助我們做到這一點。它們用于識別和定義各種界限上下文和聚合之間的關系。在上面的例子中,界限上下文定義了模型的邊界(價格、折扣等等)。而上下文映射定義了這些模型之間以及不同上下文之間的關系。在確定了這些依賴關系之后,我們就可以確定下來實現這些服務的團隊之間的正確協作模型了。

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

    購物車上下文負責訂單的在線授權;訂單上下文處理訂單履行完成后的支付流程,如結算;聯絡中心處理任何異常情況,如支付重試和變更訂單使用的支付方式。為了簡單起見,我們假設所有這些上下文都是作為單獨的服務實現的,所有這些上下文封裝了同一個模型。請注意,這些模型在邏輯上是相同的。也就是說,它們都遵循相同的統一領域語言——支付方式、授權和結算。只是它們是不同上下文的一部分。

    另一個跡象表明,同一個模型在不同的上下文中傳播,所有這些模型都直接與單個支付網關相集成,并且彼此執行相同的操作。

    圖 4:定義錯誤的上下文映射

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

    在上面的設計中有一些非常明顯的問題(圖 4)。支付聚合是多個上下文的一部分。在各種服務之間強制執行不變性和一致性是不可能的,更不用說這些服務之間的并發問題了。例如,如果在訂單服務嘗試按之前提交的付款方式進行結算的過程中,聯絡中心更改了與訂單關聯的付款方式會發生什么情況。另外,請注意,支付網關中的任何更改都將迫使對多個服務進行更改,可能會涉及到多個團隊,因為它們共同擁有這些上下文。

    通過一些調整并將聚合與正確的上下文對齊,我們就可以更好地表示這些子域了(圖 5)。需要進行很多的更改。

    我們來看下更改的點:

  • 支付聚合有了一個新家——支付服務。該服務還從其他需要支付服務的服務中提取了支付網關。由于單個界限上下文現在擁有了單個聚合,所以不變量很容易管理;所有事務都在同一個服務的邊界內進行,從而避免了任何并發問題。

  • 支付聚合使用了反腐層(ACL)將核心領域模型與支付網關的數據模型隔離開來,后者通常是由第三方提供的,可能會發生變化。在以后的文章中,我們將深入研究基于“端口和適配器”模式的應用程序設計。ACL 層通常包含將支付網關的數據模型轉換為支付聚合數據模型的適配器。

  • 購物車服務通過直接調用 API 的方式來調用支付服務,因為當客戶在網站上購物時,購物車服務需要完成支付授權。

  • 記錄訂單和支付服務之間的交互作用。訂單服務發出一個域事件(稍后會在本文中對此進行詳細介紹)。支付服務監聽此事件并完成訂單的結算

  • 聯絡中心服務可能有許多聚合,但我們只對該用例中的訂單聚合感興趣。當更改付款方式時,此服務發出一個事件,支付服務將通過以下方式對此事件做出響應:將先前使用的信用卡撤銷,再處理新的信用卡。

  • 圖 5:重新定義的上下文映射

    通常,單體或遺留應用程序有許多聚合,且邊界重疊。創建這些聚合及其依賴關系的上下文映射,將有助于我們理解從這些單體應用中獲取任何新微服務的輪廓。請記住,微服務架構的成敗取決于聚合之間的低耦合以及聚合之內的高內聚。

    還需要注意的是,界限上下文本身就是合適的內聚單元。即使上下文有多個聚合,也可以將整個上下文及其聚合組成單個微服務。我們發現這種啟發式方法對于有些模糊的領域特別有用,比如組織正在涉足的新業務領域。我們可能對分離的正確邊界沒有足夠的了解,并且任何過早的聚合分解都可能導致昂貴的重構。試想一下,由于數據遷移,不得不將兩個數據庫合并為一個,因為我們偶然發現兩個聚合屬于同一類。但是要確保這些聚合通過接口是充分隔離的,這樣它們就不知道彼此的復雜細節了。

    事件風暴:另一種識別服務邊界的技術

    事件風暴(Event Storming)是識別系統中聚合(以及微服務)的另一種必不可少的技術。它是一個非常有用的工具,既可用于分解單體應用,也可用于設計復雜的微服務生態系統。我們已經使用這種技術分解了一個復雜的應用程序,并打算寫一篇單獨的文章來介紹我們的事件風暴經驗。在本文中,我們只給出一個快速的高層概述。

    簡而言之,事件風暴是在應用程序團隊(這里,指單體)中進行的頭腦風暴,以識別系統中發生的各種領域事件和流程。團隊還需確定這些事件影響的總和或模型,以及由此產生的任何后續影響。當團隊做這個頭腦風暴時,他們將識別不同的重疊概念、模棱兩可的領域語言和相互沖突的業務流程。他們對相關的模型進行分組,重新定義聚合并識別重復的流程。隨著這些工作的進行,這些聚合所屬的界限上下文變得清晰起來。如果所有團隊都在同一個房間(物理或虛擬)里,并開始在 Scrum 風格的白板上繪制事件、命令和流程的映射,那么事件風暴研討就會非常有用。在本練習結束時,通常會產出如下成果:

  • 重新定義的聚合列表。這些可能會成為新的微服務

  • 需要在這些微服務之間流動的領域事件

  • 其他應用程序或用戶直接調用的命令

  • 下面是我們在一次事件風暴研討會結束時產生的一個示例樣板。對于團隊來說,就正確的聚合和界限上下文達成一致是一次很棒的協作活動。此外,團隊在本次會議結束時還對領域、統一語言和精確的服務邊界有著共同的理解。

    圖 6:事件風暴板

    微服務之間的通信

    快速回顧一下,一個單體應用在單個流程邊界內擁有多個聚合。因此,可以在這個邊界內管理各個聚合的一致性。例如,如果客戶下了訂單,我們可以減少商品庫存,并向客戶發送電子郵件,所有這些都在一個事務中完成。所有操作要么都成功,要么都會失敗。但是,當我們分解了單體并將聚合分散到不同的上下文中時,我們將擁有數十個甚至數百個微服務。但目前為止,存在于單體應用單一邊界內的流程,現在被分散到了多個分布式系統中。要在所有這些分布式系統中實現事務的完整性和一致性是非常困難的,而且要以系統的可用性為代價。

    微服務也是分布式系統。因此,CAP 定理也適用于它們:“一個分布式系統只能提供三個所需特性中的兩個:一致性、可用性和分區容錯(CAP 中的‘C’——Consistency、‘A’——Availability 和 ‘P’——Partition Tolerance)。”在現實世界的系統中,分區容錯是不可協商的——網絡是不可靠的、虛擬機可以宕機、區域之間的延遲可能會變得更糟等等。

    因此,我們可以選擇“可用性”或“一致性”。現在,我們知道,在任何現代應用程序中,犧牲“可用性”也不是一個好主意。

    圖 7:CAP 定理

    圍繞最終一致性設計應用程序

    如果我們想要跨多個分布式系統構建事務,那么我們將再次陷入單體應用的困境。但這一次它會是最糟糕的一種單體:一個分散的單體應用。如果這些系統中的任何一個變得不可用,則整個流程不可用,通常會導致極差的客戶體驗、承諾的失敗等等。此外,對一個服務的變更通常會導致另一個服務的變更,從而引起復雜和昂貴的部署。因此,我們最好根據自己的用例來設計應用程序,容忍稍微的不一致,以提高可用性。對于上面的例子,我們可以使所有流程異步,從而達到最終的一致性。我們可以獨立于其他流程,異步發送電子郵件;如果已經承諾的商品以后在倉庫中不可用,那么該商品可能需要補貨,或者我們可以停止接受超出某個閾值的該商品的訂單。

    有時,我們可能會遇到這樣的場景:可能需要跨越不同流程邊界的兩個聚合的強 ACID 式的事務。這是一個重新審視這些聚合并將它們組合成一個聚合的極好跡象。開始在不同流程邊界中分解這些聚合之前,事件風暴和上下文映射將有助于我們及早識別這些依賴關系。將兩個微服務合并為一個的成本很高,這是我們應該努力避免的。

    支持事件驅動的架構

    微服務可以將發生在其聚合上的基本更改發出來,這些稱為領域事件(Domain Event),并且任何對這些更改感興趣的服務都可以監聽這些事件并在其領域內執行相應的操作。這種方法避免了任何行為上的耦合(一個領域無需規定其他領域應該做什么)以及時間上的耦合(一個流程的成功完成不依賴于所有系統同時可用)。當然,這將意味著系統最終是一致的。

    圖 8:事件驅動架構

    在上面的示例中,訂單服務發布一個事件:訂單已取消。訂閱了該事件的其他服務處理各自的領域功能:支付服務退款,庫存服務調整商品的庫存,等等。為確保此集成的可靠性和彈性,需要注意以下幾點:

  • 生產者應確保至少發出了一次事件。如果過程中出現失敗,則應確保存在回退機制以重新觸發事件

  • 消費者應該確保以冪等的方式消費事件。如果再次發生同一事件,不會對消費者產生任何副作用。事件也可能不是順序到達的。消費者可以使用時間戳或版本號字段來保證事件的唯一性。

  • 由于某些用例的特性,不一定總是可以使用基于事件的集成。請看一下購物車服務和支付服務之間的集成。這是一個同步集成,因此我們需要注意一些事項。這是一個行為耦合的例子——購物車服務可能會從支付服務調用一個 REST API,并指示它授權訂單的支付,而時間耦合——支付服務需要對購物車服務可用,它才能接受訂單。這種耦合降低了這些上下文的自治性,也可能會引入不必要的依賴。有幾種方法可以避免這種耦合,但是如果使用了所有這些選項,我們將失去向客戶提供即時反饋的能力。

  • 將 REST API 轉換為基于事件的集成。但是,如果支付服務僅公開 REST API,則此選項可能不可用

  • 購物車服務立即接受訂單,并且有一個批處理作業來接管訂單并調用支付服務 API

  • 購物車服務生成一個本地事件,然后調用支付服務 API

  • 在出現失敗和上游依賴項(支付服務)不可用的情況下,將上述方法與重試相結合,可以產生更具彈性的設計。例如,在發生故障的情況下,可以通過基于事件或批處理的重試來備份購物車和支付服務之間的同步集成。這種方法會對客戶體驗產生額外的影響:客戶可能輸入了不正確的支付詳細信息,當我們離線處理支付時,無法強制他們在線?;蛘?#xff0c;收回失敗的支付可能會增加企業的成本。但在所有可能的情況下,讓購物車服務對支付服務的不可用性或故障具有彈性,利大于弊。例如,如果我們無法離線收款,我們可以通知客戶。簡而言之,在用戶體驗、彈性和運營成本之間存在著權衡,在設計系統時,明智的做法是充分考慮這些折衷。

    避免為了滿足調用者的特定數據需求而編排服務

    存在于任何面向服務架構的一個反模式是:服務迎合調用者的特定訪問模式。通常,當調用者團隊與服務提供者團隊緊密合作時,就會發生這種情況。如果團隊正在開發一個單體應用程序,它們通常會創建一個跨越不同聚合邊界的 API,從而使這些聚合緊密耦合在一起。我們來看一個例子。比如說 Web 中的訂單詳情頁面,移動應用程序需要在單個頁面上顯示訂單詳情和訂單退款處理詳情。在一個單體應用程序中,訂單獲取 API(Order-GET-API,假設它是 REST API)需要同時查詢訂單和退款,合并兩個聚合并向調用方發送一個復合響應。由于聚合屬于同一流程邊界,因此可以在沒有太多開銷的情況下實現這一點。調用者可以在一次會話中獲得所需的所有數據。

    如果訂單和退款是不同上下文的一部分,那么數據不再出現在單個微服務或聚合邊界內。為調用者保留相同功能的一個選項是,讓訂單服務負責調用退款服務并創建一個復合響應。這種方法會引起以下幾個問題:

  • 訂單服務現在與另一個服務集成,純粹是為了支持那些需要退款數據和訂單數據的調用者?,F在訂單服務的自治性降低了,因為退款聚合的任何更改都會導致訂單聚合的更改。

  • 訂單服務有另一個集成,因此需要考慮另一個故障點:如果退款服務出現故障,訂單服務是否仍可以發送部分數據,并且調用者是否可以優雅地處理故障呢?

  • 如果調用者需要變更,以從退款聚合中獲取更多的數據,那么現在需要兩個團隊同時進行變更

  • 如果跨平臺都遵循這種模式,則可能會導致各種域服務之間形成復雜的依賴關系網,這都是因為這些服務迎合了調用者特定的訪問模式。

  • 專門服務于前端的后端(BFFs)

    一種減輕這種風險的方法是讓調用者團隊管理各種域服務之間的編排。畢竟,調用方更了解訪問模式,并且可以完全控制對這些模式的任何更改。這種方法將域服務從表示層解耦出來,讓它們專注于核心業務流程。但是,如果 Web 和移動應用程序開始直接調用不同的服務,而不是從單體中調用一個復合 API,這可能會給這些應用程序帶來性能開銷——在較低帶寬的網絡上進行多次調用,處理和合并來自不同 API 的數據,等等。

    相反,可以使用另一種稱為用于前端的后端模式(Backend for Front-ends)。在這種設計模式中,由消費者創建和管理的后端服務,在本例中是 Web 和移動團隊,它負責對多個域服務進行集成,純粹是為了向客戶提供前端體驗。Web 和移動團隊現在可以根據它們所需要的用例來設計數據契約。它們甚至可以使用 GraphQL 而不是 REST API 來靈活地查詢并獲取所需的內容。需要注意的是,該服務是由消費者團隊擁有和維護的,而不是提供域服務的團隊。前端團隊現在可以根據它們的需求進行優化——移動應用程序可以請求更小的有效負載,減少來自移動應用程序的會話次數,等等。下面是修訂后的業務流程圖。BFF 服務現在為其用例調用“訂單”和“退款”域服務。

    圖 9:用于前端的后端

    盡早構建 BFF 服務也很有用,這樣可以避免從單體系統中分解出過多的服務。否則,要么域服務必須支持域間編排,要么 Web 和移動應用程序必須直接從前端調用多個服務。這兩個選項都會導致性能開銷、一次性工作以及團隊之間缺乏自治。

    相關閱讀:

    1. ?Eric Evans’ Domain Driven Design

    2.? Vaughn Vernon’s Implementing Domain Driven Design

    3.? Martin Fowler’s article on Microservices

    4.? Sam Newman’s Building Microservices

    5.? Event storming

    7.? Backend for Frontends

    8.? Fallacies of distributed computing

    總結

    以上是生活随笔為你收集整理的如何基于 DDD 构建微服务?的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。