雲原生與 Go 微服務實戰 - 微服務劃分在項目中的實踐案例
今天我們主要來介紹貨運平臺應用的微服務劃分的案例。
首先,先建立微服務劃分應用 DDD 的一些基本原則:
你的微服務是否太小?或者太緊密耦合?本設計指南可以提供幫助。
設計微服務往往更像是一門藝術而不是科學。這裏提出五個建議:
-
它不會與其他服務共享數據庫表
-
它擁有最少量的數據庫表
-
它設計爲有狀態的或無狀態的
-
其數據可用性需求
-
這是真相的唯一來源
避免任意規則
在設計和創建微服務時,不要陷入使用任意規則的陷阱。
精心設計的服務的特點
如果您已閱讀過有關微服務的文章,毫無疑問,您會發現有關設計良好的服務的建議。簡而言之:高凝聚力和鬆散耦合。
如何劃分微服務界限,下面爲你們提供了一些潛在的特性。
特性#1:它不會與其他服務共享數據庫表
當設計一個微服務時,如果你有多個引用同一個表的服務,這是一個紅色警告,因爲它可能意味着你的數據庫是耦合的來源。
“每個服務都應該有自己的表 [並且] 不應共享數據庫表。” - Darby Frey,Lead Honestly 共同創始人
這實際上是關於服務與數據的關係,這正是 Elastic Swiftype SRE 的負責人 Oleksiy Kovrin 告訴我的:
“我們在開發新服務時使用的主要基本原則之一是它們不應該跨越數據庫邊界。每項服務都應該依靠自己的一套底層數據存儲。這使我們能夠集中訪問控制,審計日誌記錄,緩存邏輯等等,“他說。
Kovyrin 繼續解釋說,如果數據庫表的一部分 “與數據集的其餘部分沒有或很少有關係,這是一個強烈的信號,即組件可能可以被隔離到一個單獨的 API 或單獨的服務中。”
特性#2:它具有最少量的數據庫表
微服務的理想尺寸應該足夠小,但不能過小一點。每個服務的數據庫表的數量也是一樣。
Scaylr 工程負責人 Steven Czerwinski 在接受採訪時向我解釋說,Scaylr 的甜蜜點是 “一個服務 + 一個或兩個數據庫表”。
特點#3:它有設計爲有狀態或無狀態
在設計微服務時,您需要問自己是否需要訪問數據庫,或者它是否將成爲處理 TB 數據(如電子郵件或日誌)的無狀態服務。
“我們通過定義服務的輸入和輸出來定義服務的邊界。有時服務是網絡 API,但它也可能是一個處理輸入文件並在數據庫中生成記錄的過程(這是我們的日誌處理服務的情況)“ - Julien Lemoine
要清楚這個前沿,它會導致更好的設計服務。
特點#4:它的數據可用性需求被考慮在內
在設計微服務時,您需要記住哪些服務將依賴於這項新服務,以及如果數據不可用,對系統的影響是什麼。考慮到這一點,您可以爲此服務正確設計數據備份和恢復系統。
當與 Steven Czerwinski 談話時,他提到他們的關鍵客戶行空間映射數據由於其重要性而以不同方式複製和分離到不同分區。
“而每個分片信息,都是在自己的小分區中。如果所在分區宕機,那麼就沒有備份可用,但它隻影響 5%的客戶,而不是 100%的客戶,“Czerwinski 解釋說。
特點#5:這是一個真理的單一來源
要牢記的最後一個特點是設計一個服務,使其成爲系統中某件事情的唯一真理來源。
舉例來說,當您從電子商務網站訂購某物品時,會生成訂單 ID。此訂單 ID 可供其他服務用於查詢訂單服務以獲取有關訂單的完整信息。使用 pub / sub 概念,在服務之間傳遞的數據應該是訂單 ID,而不是訂單本身的屬性 / 信息。只有訂單服務具有完整的信息,並且是給定訂單的唯一真實來源。
考慮更大的團隊
對於大型系統而言,在確定服務邊界時,組織架構考慮將發揮作用。有兩點需要注意:獨立發佈時間表和不同的上線時間的重要性。
Cloud66 首席執行官 Khash Sajadi 表示:“我們所見過的最成功的微服務實施要麼基於軟件設計原則,例如基於領域驅動設計、面向服務架構 SOA 或反映組織方式的架構。
“所以對於支付團隊來說,”Sajadi 繼續說道,“他們有支付服務或信用卡驗證服務,這是他們向外界提供的服務。這主要是關於向外界提供更多服務的業務部門。“
“[亞馬遜 CEO: 傑夫貝佐斯] 提出了'兩個比薩餅'的規則 - 一個團隊不能多到兩個披薩餅還不夠他們喫的地步。” - Iron.io 首席技術官 Travis Reeder
亞馬遜是擁有多個團隊的大型組織的完美典範。正如在 API 推薦人發表的一篇文章中提到的,傑夫貝佐斯向所有員工發佈了一份授權通知他們,公司內的每個團隊都必須通過 API 進行溝通。任何不會的人將被解僱。
這樣,所有的數據和功能都通過接口暴露出來。貝佐斯還設法讓每個團隊解耦,定義他們的資源,並通過 API 使其可用。亞馬遜總是自底而上從頭開始建立一個系統。這可以讓公司內的每個團隊成爲彼此的合作伙伴。
與 Iron.io 的首席技術官 Travis Reeder 談到了貝佐斯的內部計劃。
“傑夫貝佐斯強制所有 team 都必須建立 API 來與其他 team 進行溝通,他也提出了'兩個披薩'規則,一個團隊不能多到兩個披薩餅還不夠他們喫的地步。” 他說。
“我認爲這同樣適用於這樣情況:當一個小團隊在開發、管理和生產方面開始變得笨拙或開始變慢,這說明這個團隊可能已經太大了,“
如何判斷服務是否太小,或許沒有正確定義
在微服務系統的測試和實施階段,需要牢記下面兩條出現現象。
要注意的第一個現象是服務之間的任何過度依賴。 如果兩個服務不斷地互相調用,那麼這已經是一個強烈的耦合信號,他們如果併成一個服務可能更好。
第二個現象:建立服務的開銷超過了讓其獨立的好處。 在這種情況下不如合併成一個服務。
Darby Frey 解釋說:“每個應用程序需要將其日誌彙總到某處並需要進行監控。您需要設置報警。然後需要有標準的響應操作程序,並在事情中斷時運行。你必須管理 SSH 的訪問權限。爲了讓應用程序正常運行,必須準備大量基礎設施支持。“
建立了這些認知之後,我們再來看一個實際案例:
建立貨運平臺需求描述
項目實踐最好是基於相對真實的業務開展,這樣才能使你在項目實踐中學到的技巧更好應用到實際開發中。
本節的後續項目實踐中,我們將會以一個貨運平臺應用作爲微服務實戰項目,它需要滿足以下條件:
-
具備一定的複雜性,能夠演示每個微服務的作用;
-
業務邏輯不會過於複雜,避免你在相關專業範疇的理解上浪費過多的時間;
-
具備一定的真實性,這能夠幫助你將實戰中學到各種知識直接應用到正式的開發環境中。
1. 收集整理平臺應用的需求
比如,現在有一家貨運公司,它在每個城市都設立了對應的碼頭,客戶可以在碼頭上寄送貨物,還可以預定寄送的貨物、寄送的目的地等。在這個寄送過程中,貨運公司會根據寄送貨物的相關信息分配對應的行程在各個碼頭中流轉,最終送達目的地碼頭。客戶可以在貨物寄送的任意節點查看跟蹤貨物,並最後在目的地碼頭上領取貨物。
這時貨運公司就需要開發一個貨運平臺應用,幫助它管理碼頭的信息和貨物的流轉行程,並提供一定的方式讓客戶查詢貨物的流轉情況。
**2. **分析出平臺的主要功能:
-
碼頭管理 ,貨運公司通過應用添加位於不同城市的碼頭;
-
路線管理 ,不同碼頭之間的流轉路線需要在系統中指定,貨物需要在指定的路線上在不同的碼頭上流轉;
-
貨物管理 ,包括貨物寄送、貨運流轉記錄、貨物提取等;
-
費用計算 ,根據路線和貨物的信息計算寄送成本。
下面我們就根據上述需求來劃分領域和限界上下文。
**3. **領域劃分
DDD 是以領域爲核心的,實踐 DDD 時要首先根據問題域劃分出相關的領域,描述應用需要解決什麼樣的問題。領域中存在限界上下文,它用於解決領域內的特定問題,具備特定的職責,並存在邊界。限界上下文內的領域模型具備較高的內聚性,不同邊界之間需要透過顯示邊界進行通信。
在根據需求劃分限界上下文時**:**
-
從需求術語中提取一些概念對象,爲它們綁定一定行爲。
-
接着將緊耦合的對象劃分在一起,尋找它們的內在關係,嘗試形成對應的限界上下文。
-
最後,整理各個限界上下文的行爲,驗證它是否完整、清晰和高內聚。
從上面的需求描述我們可以簡單地將用戶分爲貨運人員和客戶:貨運人員需要藉助應用記錄貨物在各個碼頭中流轉的過程,配置碼頭之間的流轉路線,管理碼頭;而客戶並不關心這些細節,他們僅關心他們的貨物到了哪裏。根據這樣的需求特點,我們先將貨運平臺應用劃分爲客戶端貨物查詢領域和管理端貨運平臺領域(如下圖)。
貨運平臺領域示意圖
在確定了客戶端和管理端的領域後,我們需要進一步對每個領域進行子域和限界上下文劃分,這裏主要介紹管理端貨運平臺領域的子域和上下文劃分。
從前面的需求中,我們可以抽取一些關鍵性概念作爲子域,幫助我們形成限界上下文。
貨運公司希望藉助貨運平臺幫助他們解決貨物流轉的問題,跟蹤貨物在不同碼頭之間的流轉情況,這是應用需要解決的核心問題,也是核心領域所在,我們可以認爲貨運就是核心領域。貨運上下文作爲整個應用的核心,對貨物的流轉過程進行管理,包括寄送貨物、流轉貨物、登記貨物和提取貨物等功能,有貨物、貨運單和貨運記錄等概念。
貨物是在不同城市的碼頭之間進行流轉,貨運公司會在碼頭內對流轉的貨物進行登記。在擴展或者調整業務時,公司還會對碼頭進行增刪改查等基本操作。碼頭作爲一個子域,支撐貨運核心領域,碼頭上下文主要解決碼頭的管理問題。
不同碼頭之間流轉需要指定對應的路線,不能盲目運輸;在寄送貨物時也需要根據寄送的起點和目的地計算是否運輸可達和相應的運輸路線。這部分領域問題劃分爲貨運許可子域,由貨運許可上下文提供解決的領域模型。
計費上下文用於計算貨物運輸的費用,涉及貨運、碼頭和貨運許可等多個子域。
最後我們就細分出了管理端領域和限界上下文(如下圖所示),貨運、碼頭、貨運許可和計費這 4 個限界上下文將通過它們邊界內的領域模型提供特定的解決方案。
管理端領域和限界上下文示意圖
在創建微服務時,我們希望每一個微服務都是單一職責的,並具備高內聚、低耦合的特徵,限界上下文完美匹合這個特徵。因此,我們可以將限界上下文與微服務進行一一對應。
那麼我們的貨運平臺應用的管理端就可以據此劃分爲 4 個微服務,分別是:
貨運微服務(transport)
碼頭微服務(wharf)
貨運許可微服務(permit)
計費微服務(charging)
4. 領域上下文的戰術設計
戰術設計是對限界上下文的具體化和細緻化,涉及具體領域對象的設計,這直接關係到技術層面的實現,往往是開發人員的主要關注點。前面我們劃分出了貨運平臺管理端的限界上下文,接下來我們就來解析限界上下文內部的組織關係。
領域模型是對領域內的關鍵概念及其關係的可視化表示,它通過描述多個領域對象之間的關係,實現業務功能場景在軟件系統的映射,爲領域問題提供解決方案。戰術設計的關鍵在於抽象出限界上下文中的領域對象及其關係,並通過領域模型圖表示出來。
-
領域對象能夠表達出業務意圖,它們具備數據和行爲,包括實體、值對象和聚合等概念:
-
實體是一種對象,有唯一的標識識別自身,具備一定的生命週期,並在生命週期內根據狀態提供不同的行爲,一般需要考慮實體的持久化問題。
-
值對象沒有唯一的標識,在領域模型中是不可變和可共享的。
-
聚合由一系列實體和值對象內聚而成,用以表達一個完整的領域概念。每個聚合都存在一個聚合根(根實體)。
在貨運上下文中,我們通過貨運這個聚合來控制貨運行爲,貨運就是這個聚合的根實體,貨運上下文的建模如下圖所示:
貨運上下文數據項模型圖
在上圖中,引用類的實體和值對象來自其他上下文。貨運實體有貨運單 ID 這個唯一標識,它由貨物實體、貨運記錄值對象、貨運路線實體的引用、起始碼頭實體的引用、目的地碼頭實體的引用、寄貨人實體和費用等組成,其中貨運記錄值對象記錄了貨物在多個碼頭中的流轉過程,貨物實體記錄了需要運送貨物的相關信息。
貨運上下文關注的實體爲碼頭,主要對外提供碼頭的管理功能。碼頭上下文的建模如下圖所示,碼頭實體包括碼頭的承載量、可容納的貨物類型、碼頭地址等屬性。
碼頭上下文數據項模型圖
貨運許可上下文的聚合根爲貨運路線,主要解決貨運路線的管理和選擇問題,建模如下所示:
貨運許可上下文數據項模型圖
貨運路線聚合由貨運路線 ID、起始碼頭實體引用、目的地碼頭實體引用、中轉路線值對象、運轉狀態值對象和路線日誌實體組成。其中,中轉路線值對象描述了貨運路線整個流轉過程,包括從哪個碼頭出發、中間經過哪個碼頭、最終到達哪個碼頭、通過什麼交通工具,以及預計要花多長時間等信息。運轉狀態描述了當前貨運路線的運行情況。路線日誌記錄了每次貨運路線運轉情況,以方便後續根據數據調整貨運路線。
計費上下文更多是費用計算的業務邏輯和算法,我們可以通過一個領域服務暴露它提供的行爲。
5. 多個限界上下文進行集成**,才能完整解決領域內的問題**。考慮到限界上下文的各種概念存在邊界隔離的問題,在集成時需要注意不同限界上下文中領域概念的映射和翻譯,避免對我們已經設計好的領域模型造成污染。
限界上下文主要有以下幾種映射方式:
-
合作關係(Partnership):兩個上下文緊密合作的關係,一榮俱榮,一損俱損。
-
共享內核(Shared Kernel):兩個上下文依賴部分共享的模型。
-
防腐層(Anticorruption Layer):一個上下文通過一些適配和轉換與另一個上下文交互。
-
客戶方 - 供應方開發(Customer-Supplier Development):上下文之間有組織的上下游依賴。
-
開放主機服務(Open Host Service):定義一種協議來讓其他上下文對本上下文進行訪問。
-
遵奉者(Conformist):下游上下文只能盲目依賴上游上下文。
-
發佈語言(Published Language):通常與 OHS 一起使用,用於定義開放主機的協議。
-
大泥球(Big Ball of Mud):混雜在一起的上下文關係,邊界不清晰。
-
另謀他路(Separate Way):兩個完全沒有任何聯繫的上下文。
而在我們的實踐中,主要通過防腐層映射不同的限界上下文中相同的領域對象,保證整體領域概念的完整和統一。
一般來講,會把領域對象的行爲和數據都放在一起,以達到設計上高內聚和低耦合的目的,並屏蔽領域對象的細節,暴露公開的行爲方法。當存在一些不歸屬於任何領域對象的領域行爲或操作時,我們就將它們放在領域服務中。
領域服務是領域模型的一部分,應該儘量避免將過多的領域行爲放在領域服務中,因爲這會造成領域對象變成 “貧血對象”,而一個合格的領域對象應該是行爲和數據都兼併的。在我們微服務實踐項目中,考慮到服務暴露粒度的問題,我們會把上下文暴露的領域行爲都放到領域服務中,包括各個上下文的聚合根對外暴露的領域行爲。
貨運平臺應用管理端的各個限界上下文包含的領域服務和職責有如下:
TransportService,創建新的貨運、貨運記錄,查詢貨運狀態等;
WharfService,增加碼頭、刪除碼頭、修改碼頭信息和查看碼頭等;
PermitService,增加新的貨運路線、修改貨運路線、刪除貨運路線、爲貨運查找貨運路線和記錄貨運運轉狀態等;
ChargingService,計算貨運費用。
後續我們展開微服務業務開發時將按照上述構建的領域模型進行設計,在實踐中驗證領域模型的可行性,並調整不合理的地方。
小結
在本節中,我們基於預設的貨運平臺應用需求,運用 DDD 進行該平臺管理端的微服務劃分設計:
使用 DDD 戰略設計進行領域劃分,劃分出貨運、碼頭、貨運許可、計費等限界上下文,並初步映射出貨運微服務、碼頭微服務、貨運許可微服務和計費微服務;
使用戰術設計,對每個限界上下文進行細分,梳理內部的領域對象交互邏輯,建立領域模型,並初步設計每個領域模型對象暴露的領域行爲。
期望在一個課時中講清楚領域驅動設計是不太現實的想法,但我希望通過本課時,可以加深你對領域驅動設計的理解和應用。在接下來的微服務實踐中,我們將根據上述劃分的微服務具體展開微服務組件的實踐。
最後,關於領域驅動設計實踐,你有什麼經驗和想法?
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/441uiHwsIFoAH78eSYEFmA