您好,登錄后才能下訂單哦!
拆分單體應用為服務的難點
從表面上看,通過定義與業(yè)務能力或子域相對應的服務來創(chuàng)建微服務架構的策略看起來很簡單。但是,你可能會遇到幾個障礙:
網(wǎng)絡延遲。
同步進程間通信導致可用性降低。
在服務之間維持數(shù)據(jù)一致性。
獲取一致的數(shù)據(jù)視圖。
上帝類阻礙了拆分。
讓我們來看看每個問題,先從網(wǎng)絡延遲開始。
網(wǎng)絡延遲
網(wǎng)絡延遲是分布式系統(tǒng)中一直存在的問題。你可能會發(fā)現(xiàn),對服務的特定分解會導致兩個服務之間的大量往返調(diào)用。有時,你可以通過實施批處理API在一次往返中獲取多個對象,從而將延遲減少到可接受的數(shù)量。但在其他情況下,解決方案是把多個相關的服務組合在一起,用編程語言的函數(shù)調(diào)用替換昂貴的進程間通信。
同步進程間通信導致可用性降低
另一個需要考慮的問題是如何處理進程間通信而不降低系統(tǒng)的可用性。例如,實現(xiàn)createOrder()操作最常見的方式是讓Order Service使用REST同步調(diào)用其他服務。這樣做的弊端是REST這樣的協(xié)議會降低Order Service的可用性。如果任何一個被調(diào)用的服務處在不可用的狀態(tài),那么訂單就無法創(chuàng)建了。有時候這可能是一個不得已的折中,但是在第3章中學習異步消息之后,你就會發(fā)現(xiàn)其實有更好的辦法來消除這類同步調(diào)用產(chǎn)生的緊耦合并提升可用性。
在服務之間維持數(shù)據(jù)一致性
另一個挑戰(zhàn)是如何在某些系統(tǒng)操作需要更新多個服務中的數(shù)據(jù)時,仍舊維護服務之間的數(shù)據(jù)一致性。例如,當餐館接受訂單時,必須在Kitchen Service和Delivery Service中同時進行更新。Kitchen Service會更改Ticket的狀態(tài)。Delivery Service安排訂單的交付。這些更新都必須以原子化的方式完成。
傳統(tǒng)的解決方案是使用基于兩階段提交(two phase commit)的分布式事務管理機制。但正如你將在第4章中看到的那樣,對于現(xiàn)今的應用程序而言,這不是一個好的選擇,你必須使用一種非常不同的方法來處理事務管理,這就是Saga。Saga是一系列使用消息協(xié)作的本地事務。Saga比傳統(tǒng)的ACID事務更復雜,但它們在許多情況下都能工作得很好。Saga的一個限制是它們最終是一致的。如果你需要以原子方式更新某些數(shù)據(jù),那么它必須位于單個服務中,這可能是分解的障礙。
獲取一致的數(shù)據(jù)視圖
分解的另一個障礙是無法跨多個數(shù)據(jù)庫獲得真正一致的數(shù)據(jù)視圖。在單體應用程序中,ACID事務的屬性保證查詢將返回數(shù)據(jù)庫的一致視圖。相反,在微服務架構中,即使每個服務的數(shù)據(jù)庫是一致的,你也無法獲得全局一致的數(shù)據(jù)視圖。如果你需要一些數(shù)據(jù)的一致視圖,那么它必須駐留在單個服務中,這也是服務分解所面臨的問題。幸運的是,在實踐中這很少帶來真正的問題。
上帝類阻礙了拆分
分解的另一個障礙是存在所謂的上帝類。上帝類是在整個應用程序中使用的全局類。上帝類通常為應用程序的許多不同方面實現(xiàn)業(yè)務邏輯。它有大量字段映射到具有許多列的數(shù)據(jù)庫表。大多數(shù)應用程序至少有一個這樣的上帝類,每個類代表一個對領域至關重要的概念:銀行賬戶、電子商務訂單、保險政策,等等。因為上帝類將應用程序的許多不同方面的狀態(tài)和行為捆綁在一起,所以將使用它的任何業(yè)務邏輯拆分為服務往往都是一個不可逾越的障礙。
Order類是FTGO應用程序中上帝類的一個很好的例子。這并不奇怪:畢竟FTGO的目的是向客戶提供食品訂單。系統(tǒng)的大多數(shù)部分都涉及訂單。如果FTGO應用程序具有單個領域模型,則Order類將是一個非常大的類。它將具有與應用程序的許多不同部分相對應的狀態(tài)和行為。圖6顯示了使用傳統(tǒng)建模技術創(chuàng)建的Order類的結(jié)構。
圖6 Order這個上帝類承載了太多的職責
如你所見,Order類具有與訂單處理、餐館訂單管理、送餐和付款相對應的字段及方法。由于一個模型必須描述來自應用程序的不同部分的狀態(tài)轉(zhuǎn)換,因此該類還具有復雜的狀態(tài)模型。在目前情況下,這個類的存在使得將代碼分割成服務變得極其困難。
一種解決方案是將Order類打包到庫中并創(chuàng)建一個中央Order數(shù)據(jù)庫。處理訂單的所有服務都使用此庫并訪問訪問數(shù)據(jù)庫。這種方法的問題在于它違反了微服務架構的一個關鍵原則,并導致我們特別不愿意看到的緊耦合。例如,對Order模式的任何更改都要求其他開發(fā)團隊同步更新和重新編譯他們的代碼。
另一種解決方案是將Order數(shù)據(jù)庫封裝在Order Service中,該服務由其他服務調(diào)用以檢索和更新訂單。該設計的問題在于這樣的一個Order Service將成為一個純數(shù)據(jù)服務,成為包含很少或沒有業(yè)務邏輯的貧血領域模型(anemic domain model)。這兩種解決方案都沒有吸引力,但幸運的是,DDD提供了一個好的解決方案。
更好的方法是應用DDD并將每個服務視為具有自己的領域模型的單獨子域。這意味著FTGO應用程序中與訂單有關的每個服務都有自己的領域模型及其對應的Order類的版本。Delivery Service是多領域模型的一個很好的例子。如圖7所示為Order,它非常簡單:取餐地址、取餐時間、送餐地址和送餐時間。此外,DeliveryService使用更合適的Delivery名稱,而不是稱之為Order。
圖7 Delivery Service的領域模型
Delivery Service對訂單的任何其他屬性不感興趣。
Kitchen Service有一個更簡單的訂單視圖。它的Order版本就是一個Ticket(后廚工單)。如圖8所示,Ticket只包含status、requestedDeliveryTime、prepareByTime以及告訴餐館準備的訂單項列表。它不關心消費者、付款、交付等這些與它無關的事情。
圖8 Kitchen Service的領域模型
Order Service具有最復雜的訂單視圖,如圖9所示。即使它有相當多的字段和方法,它仍然比原始版本的那個Order上帝類簡單得多。
圖9 Order Service的領域模型
每個領域模型中的Order類表示同一Order業(yè)務實體的不同方面。FTGO應用程序必須維持不同服務中這些不同對象之間的一致性。例如,一旦OrderService授權消費者的信用卡,它必須觸發(fā)在Kitchen Service中創(chuàng)建Ticket。同樣,如果Kitchen Service拒絕訂單,則必須在Order Service中取消訂單,并且為客戶退款。在第4章中,我們將學習如何使用前面提到的事件驅(qū)動機制Saga來維護服務之間的一致性。
除了造成一些技術挑戰(zhàn)以外,擁有多個領域模型還會影響用戶體驗。應用程序必須在用戶體驗(即其自己的領域模型)與每個服務的領域模型之間進行轉(zhuǎn)換。例如,在FTGO應用程序中,向消費者顯示的Order狀態(tài)來自存儲在多個服務中的Order信息。這種轉(zhuǎn)換通常由API Gateway處理,將在第8章中討論。盡管存在這些挑戰(zhàn),但在定義微服務架構時,必須識別并消除上帝類。
我們現(xiàn)在來看看如何定義服務API。
6 定義服務API
到目前為止,我們有一個系統(tǒng)操作列表和一個潛在服務列表。下一步是定義每個服務的API:也就是服務的操作和事件。存在服務API操作有以下兩個原因:首先,某些操作對應于系統(tǒng)操作。它們由外部客戶端調(diào)用,也可能由其他服務調(diào)用。另次,存在一些其他操作用以支持服務之間的協(xié)作。這些操作僅由其他服務調(diào)用。
服務通過對外發(fā)布事件,使其能夠與其他服務協(xié)作。第4章將描述如何使用事件來實現(xiàn)Saga,這些Saga可以維護服務之間的數(shù)據(jù)一致性。第7章將討論如何使用事件來更新CQRS視圖,這些視圖支持有效的查詢。應用程序還可以使用事件來通知外部客戶端。例如,可以使用WebSockets將事件傳遞給瀏覽器。
定義服務API的起點是將每個系統(tǒng)操作映射到服務。之后確定服務是否需要與其他服務協(xié)作以實現(xiàn)系統(tǒng)操作。如果需要協(xié)作,我們將確定其他服務必須提供哪些API才能支持協(xié)作。首先來看一下如何將系統(tǒng)操作分配給服務。
把系統(tǒng)操作分配給服務
第一步是確定哪個服務是請求的初始入口點。許多系統(tǒng)操作可以清晰地映射到服務,但有時映射會不太明顯。例如,考慮使用noteUpdatedLocation()操作來更新送餐員的位置。一方面,因為它與送餐員有關,所以應該將此操作分配給Courier Service。另一方面,它是需要送餐地點的DeliveryService。在這種情況下,將操作分配給需要操作所提供信息的服務是更好的選擇。在其他情況下,將操作分配給具有處理它所需信息的服務可能是有意義的。表4顯示了FTGO應用程序中的哪些服務負責哪些操作。
表4 FTGO應用程序的系統(tǒng)操作映射到具體的服務
把操作分配給服務后,下一步是確定在處理每一個系統(tǒng)操作時,服務之間如何交互。
確定支持服務協(xié)作所需要的API
某些系統(tǒng)操作完全由單個服務處理。例如,在FTGO應用程序中,Consumer Service完全獨立地處理createConsumer()操作。但是其他系統(tǒng)操作跨越多個服務。處理這些請求之一所需的數(shù)據(jù)可能分散在多個服務周圍。例如,為了實現(xiàn)createOrder()操作,Order Service必須調(diào)用以下服務以驗證其前置條件并使后置條件成立:
Consumer Service:驗證消費者是否可以下訂單并獲取其付款信息。
Restaurant Service:驗證訂單行項目,驗證送貨地址和時間是否在餐廳的服務區(qū)域內(nèi),驗證訂單最低要求,并獲得訂單行項目的價格。
Kitchen Service:創(chuàng)建Ticket(后廚工單)。
Accounting Service:授權消費者的信用卡。
同樣,為了實現(xiàn)acceptOrder()系統(tǒng)操作,Kitchen Service必須調(diào)用Delivery Service來安排送餐員交付訂單。表2-3顯示了服務、修訂后的API及協(xié)作者。為了完整定義服務API,你需要分析每個系統(tǒng)操作并確定所需的協(xié)作。
表5 服務、修訂后的API及協(xié)作者
總結(jié)
微服務中的服務是根據(jù)業(yè)務需求進行組織的,按照業(yè)務能力或者子域,而不是技術上的考量。
有兩種分解模式:
按業(yè)務能力分解,其起源于業(yè)務架構。
基于領域驅(qū)動設計的概念,通過子域進行分解。
可以通過應用DDD并為每個服務定義單獨的領域模型來消除上帝類,正是上帝類引起了阻礙分解的交織依賴項。
本文摘自《微服務架構設計模式》,經(jīng)出版方授權發(fā)布。
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。