前言
這是一本關(guān)于微服務(wù)架構(gòu)設(shè)計方面的書携栋,這是本人閱讀的學(xué)習(xí)筆記。首先對一些符號做些說明:
()為補(bǔ)充挖垛,一般是書本里的內(nèi)容痒钝;
[]符號為筆者筆注;
1. 業(yè)務(wù)邏輯組織模式
組織業(yè)務(wù)邏輯有兩種主要的模式:面向過程的事務(wù)腳本模式和面向?qū)ο蟮念I(lǐng)域建模模式痢毒。
1.1 一個典型的服務(wù)架構(gòu)
業(yè)務(wù)邏輯周圍是入站和出站適配器送矩。
- 入站適配器:處理來自客戶端的請求并調(diào)用業(yè)務(wù)邏輯;
- 出站適配器:被業(yè)務(wù)邏輯調(diào)用闸准,然后它們再調(diào)用其他服務(wù)和外部程序應(yīng)用益愈;
Order Service具有六邊形架構(gòu):
此服務(wù)由業(yè)務(wù)邏輯和以下適配器組成:
- REST API adapter:入站適配器,實現(xiàn)REST API夷家,這些API會調(diào)用業(yè)務(wù)邏輯蒸其;
- OrderCommandHandlers:入站適配器,它接受來自消息代理的出站適配器库快,并調(diào)用業(yè)務(wù)邏輯摸袁;
- Database Adapter:由業(yè)務(wù)邏輯調(diào)用以訪問數(shù)據(jù)庫的出站適配器;
- Domain Event Publishing Adapter:將事件發(fā)布到消息代理的出站適配器义屏;
1.2 使用事務(wù)腳本模式設(shè)計業(yè)務(wù)邏輯
事務(wù)腳本:將業(yè)務(wù)邏輯組織為面向過程的事務(wù)腳本的集合靠汁,每種類型的請求都有一個腳本。
特點(diǎn):
- 實現(xiàn)行為的類與存儲狀態(tài)的類是分開的闽铐;
- 腳本通常在服務(wù)類中蝶怔;
- 每個服務(wù)都有一個用于請求或系統(tǒng)操作的方法;這個方法實現(xiàn)請求的業(yè)務(wù)邏輯兄墅;
1.3 使用領(lǐng)域模型模式設(shè)計業(yè)務(wù)邏輯
領(lǐng)域模型:將業(yè)務(wù)邏輯組織為由具有狀態(tài)和行為的類構(gòu)成的對象模型踢星。
特點(diǎn):
- 服務(wù)方法通常很簡單(因為服務(wù)方法幾乎總是調(diào)用持久化領(lǐng)域?qū)ο螅@些對象中包含大量業(yè)務(wù)邏輯)隙咸;
- 如:Order類具有狀態(tài)和方法沐悦,狀態(tài)是私有的成洗,只能通過它的方法間接訪問;
- 易于理解藏否、維護(hù)瓶殃、測試和擴(kuò)展;
1.4 關(guān)于領(lǐng)域驅(qū)動設(shè)計
領(lǐng)域驅(qū)動設(shè)計(DDD)是對面向?qū)ο笤O(shè)計的改進(jìn)副签,是開發(fā)復(fù)雜業(yè)務(wù)邏輯的一種方法遥椿。其基本元素如下:
- 實體(entity):具有持久化ID的對象。具有相同屬性值的兩個實體仍然是不同的對象继薛;
- 值對象(value object):作為值集合的對象修壕。具有相同屬性值的兩個值對象可以互換使用愈捅;
- 工廠(factory):負(fù)責(zé)實現(xiàn)對象創(chuàng)建邏輯的對象或方法遏考,該邏輯過于復(fù)雜,無法由類的構(gòu)造函數(shù)直接完成蓝谨。它還可以隱藏被實例化的具體類灌具。工廠方法一般可實現(xiàn)為類的靜態(tài)方法;
- 存儲庫(repository):用來訪問持久化實體的對象譬巫,儲存庫也封裝了訪問數(shù)據(jù)庫的底層機(jī)制咖楣;
- 服務(wù)(service):實現(xiàn)不屬于實體或值對象的業(yè)務(wù)邏輯對象;
2. 使用聚合模式設(shè)計領(lǐng)域模型
傳統(tǒng)領(lǐng)域模型缺少每個業(yè)務(wù)對象的明確邊界芦昔,DDD聚合旨在解決此問題诱贿。
2.1 聚合擁有明確的邊界
聚合模式:將領(lǐng)域模型組織為聚合的集合,每個聚合都是可以作為一個單元進(jìn)行處理的一組對象構(gòu)成的圖咕缎。
Order 聚合及其邊界:
-
聚合代表了一致的邊界珠十;
- 更新整個聚合可以解決一致性問題;
-
識別聚合是關(guān)鍵凭豪;
- 在驅(qū)動領(lǐng)域設(shè)計中焙蹭,設(shè)計領(lǐng)域模型的關(guān)鍵部分是識別聚合,以及它們的邊界和根嫂伞;
2.2 聚合規(guī)則
聚合規(guī)則可以確保聚合是一個可以強(qiáng)制執(zhí)行各種不變量約束的自包含單元孔厉。
-
規(guī)則一:只引用聚合根;
- 聚合根是聚合中唯一可以由外部類引用的部分帖努;客戶端只能通過調(diào)用聚合根上的方法來更新聚合撰豺;
- 如:服務(wù)使用儲存庫從數(shù)據(jù)庫加載聚合并獲取聚合根的引用;
-
規(guī)則二:聚合間的引用必須使用主鍵拼余;
- 如:Order使用consumerId引用其Consumer而不是直接引用Consumer對象污桦;
-
規(guī)則三:在一個事務(wù)中,只能創(chuàng)建或更新一個聚合姿搜;
- 這個約束可以確保單個事務(wù)的范圍不超越服務(wù)的邊界寡润;還滿足大多數(shù)NoSQL數(shù)據(jù)庫的受限事務(wù)模型捆憎;
- 這個規(guī)則讓創(chuàng)建或更新多個聚合的操作變得更加復(fù)雜,但可以通過Saga解決梭纹;
2.3 聚合的顆粒度
- 由于每個聚合的更新都是序列化的躲惰,因此更細(xì)顆粒度的聚合間提高應(yīng)用程序能同時處理的請求數(shù)量,從而提高可擴(kuò)展性变抽;
- 另一方面础拨,因為聚合是事務(wù)的范圍,所以可能需要定義更大的聚合以使特定的聚合更新操作滿足事務(wù)的原子性绍载;
- 因此诡宗,在開發(fā)領(lǐng)域模型時,必須做出的關(guān)鍵決策是決定每個聚合的大谢骼堋塔沃;
2.4 使用聚合設(shè)計業(yè)務(wù)
- 在典型的微服務(wù)中,大部分業(yè)務(wù)邏輯由聚合組成阳谍;其余業(yè)務(wù)邏輯存在與領(lǐng)域服務(wù)和Saga中蛀柴;
- Saga編排本地事務(wù)的序列,以確保數(shù)據(jù)的一致性矫夯;
- 服務(wù)是業(yè)務(wù)邏輯的入口鸽疾,由入站適配器調(diào)用;
- 服務(wù)使用存儲庫從數(shù)據(jù)庫中檢索聚合或?qū)⒕酆媳4娴綌?shù)據(jù)庫训貌;
- 每個存儲庫都由訪問數(shù)據(jù)庫的出站適配器實現(xiàn)制肮;
2.5 Order Service基于聚合設(shè)計的業(yè)務(wù)邏輯
- 業(yè)務(wù)邏輯由Order聚合、OrderService服務(wù)類递沪、OrderRepository和一個或多個Saga組成豺鼻;
- OrderService調(diào)用OrderRepository來保存和加載Order;
- 對于能在服務(wù)內(nèi)部完成處理的簡單請求区拳,服務(wù)直接更新Order聚合拘领;
- 如果更新請求跨越多個服務(wù),OrderService將創(chuàng)建一個Saga樱调;
3. 發(fā)布領(lǐng)域事件
領(lǐng)域事件:聚合在被創(chuàng)建時约素,或發(fā)生其他重大更改時發(fā)布領(lǐng)域事件。
3.1 領(lǐng)域事件的應(yīng)用場景
- 使用基于編排的Saga維護(hù)服務(wù)之間的數(shù)據(jù)一致性【第四章】笆凌;
- 通知維護(hù)數(shù)據(jù)副本的服務(wù)圣猎,源數(shù)據(jù)已經(jīng)發(fā)生了更改;這種方法稱為命令查詢職責(zé)隔離(CQRS)【第七章】乞而;
- 通過Webhook或消息代理通知不同的應(yīng)用程序送悔,以觸發(fā)下一個業(yè)務(wù)流程;
- 按順序通知同一應(yīng)用程序的不同組件;
- 向用戶發(fā)送短信或電子郵件通知欠啤,告訴他們訂單發(fā)貨荚藻、航班延誤等消息;
- 監(jiān)控領(lǐng)域事件以驗證應(yīng)用程序是否正常運(yùn)行洁段;
- 分析領(lǐng)域事件应狱,為用戶行為建模;
3.2 領(lǐng)域事件的特點(diǎn)
- 命名領(lǐng)域事件時祠丝,往往選擇動詞的過去分詞疾呻;
- 領(lǐng)域事件的每個屬性都是原始值或值對象;
- 如:OrderCreated事件類具有orderId屬性写半;
- 領(lǐng)域事件通常具有元數(shù)據(jù)岸蜗,如事件ID和時間戳;
- 元數(shù)據(jù)可以是事件對象的一部分叠蝇,可能在超類中定義璃岳;
OrderCreated事件是領(lǐng)域事件的一個例子:
-
DomainEvent
接口是一個標(biāo)識接口,用于將類標(biāo)識為領(lǐng)域事件蟆肆; -
OrderDomainEvent
是Order聚合發(fā)布的事件的標(biāo)識接口(如OrderCreated)矾睦; -
DomainEventEnvelope
是一個包含事件元數(shù)據(jù)和事件對象的類;
3.3 事件增強(qiáng)
當(dāng)OrderCreated的事件接收方需要訂單的詳細(xì)信息時炎功,一種辦法是從Order Service中檢索該信息,讓事件接收方查詢聚合服務(wù)缓溅,缺點(diǎn)是會產(chǎn)生服務(wù)請求的開銷蛇损;
另一種方案是事件增強(qiáng):
- 事件包含接收方需要的信息;
- 缺點(diǎn):可能會使領(lǐng)域事件的穩(wěn)定性降低坛怪;每當(dāng)接收方的需求發(fā)生改變時淤齐,事件類都可能需要更改;可能會降低可維護(hù)性袜匿;
3.4 識別領(lǐng)域事件
可以使用事件風(fēng)暴方法更啄,其結(jié)果是一個以事件為中心的領(lǐng)域模型,它由聚合和事件組成居灯;包括以下三個步驟:
- 頭腦風(fēng)暴:請求領(lǐng)域?qū)<壹w討論領(lǐng)域事件祭务;
- 識別事件觸發(fā)器:請求領(lǐng)域?qū)<掖_定每個事件的觸發(fā)器(如:用戶操作、外部系統(tǒng)怪嫌、另一個領(lǐng)域事件义锥、時間的流逝等);
- 識別聚合:請求領(lǐng)域?qū)<易R別那些使用命令的聚合并發(fā)出相應(yīng)的事件岩灭;
3.5 生成領(lǐng)域事件
在聚合和調(diào)用它的服務(wù)(或類)之間分配職責(zé)拌倍。
- 服務(wù)可以使用依賴注入來獲取對消息傳遞API的引用,從而輕松發(fā)布事件;
- 只要狀態(tài)發(fā)生變化柱恤,聚合就會生成事件并將它們返回給服務(wù)数初;
- 聚合可以通過以下方法將事件返回給服務(wù):
在聚合方法的返回值中包括一個事件列表:
該服務(wù)調(diào)用聚合根方法,然后發(fā)布事件梗顺;
聚合根在一個內(nèi)部字段中累積保存事件:然后服務(wù)檢索這些事件并發(fā)布它們妙真;
3.6 發(fā)布領(lǐng)域事件
服務(wù)必須使用事務(wù)性消息來發(fā)布事件,以確保領(lǐng)域事件是作為更新數(shù)據(jù)庫中聚合的事務(wù)的一部分對外發(fā)布荚守;
Eventuate Tram框架提供DomainEventPublisher接口:
[圖片上傳失敗...(image-235173-1629292536189)]
讓服務(wù)實現(xiàn)AbstractAggregateDomainEventPublisher的子類:它為發(fā)布領(lǐng)域事件提供了類型安全的接口珍德;
[圖片上傳失敗...(image-973fa8-1629292536189)]
[圖片上傳失敗...(image-2af956-1629292536189)]
3.7 消費(fèi)領(lǐng)域事件
領(lǐng)域事件是接收方使用更高級的API,如:Eventuate Tram框架的DomainEventDispatcher等矗漾。其可以將領(lǐng)域事件調(diào)度到適當(dāng)?shù)奶幚沓绦蚍椒ā?/p>
- 每當(dāng)餐館的菜單更新時锈候,KitchenServiceEventConsumer都會訂閱Restaurant Service發(fā)布事件;
- 它負(fù)責(zé)使Kitchen Service的數(shù)據(jù)副本保持最新敞贡;
4. Kichen Service的業(yè)務(wù)邏輯
該服務(wù)的主要功能是負(fù)責(zé)實現(xiàn)餐館的訂單管理功能泵琳。其兩個主要聚合是Restaurant和Ticket;
4.1 Kichen Service的設(shè)計
- 兩個聚合:
- Restaurant:知道餐館的菜單和營業(yè)事件誊役,并可以驗證訂單获列;
- Ticket:工單烹飪完成后由送餐員負(fù)責(zé)派送;
- 核心業(yè)務(wù):
- KitchenService:業(yè)務(wù)入口蛔垢,定義了創(chuàng)建和更新Restaurant及Ticket聚合的方法击孩;
- TicketRepository:定義了持久化Tickets的方法;
- RestaurantRepository:定義了持久化Restaurants的方法鹏漆;
- 三個入站適配器:
- REST API:餐館工作人員通過他們的用戶界面調(diào)用這些REST API巩梢;
- KitchenServiceCommandHandler:由Saga調(diào)用的基于異步請求 / 響應(yīng)的API;它調(diào)用KitchenService來創(chuàng)建和更新Ticket艺玲;
- KitchenServiceEventConsumer:訂閱Restaurant Service發(fā)布的事件括蝠;它調(diào)用KitchenService來創(chuàng)建和更新Restaurant聚合;
- 兩個出站適配器:
- DB Adapter:實現(xiàn)TicketRepository和RestaurantRepository接口并訪問數(shù)據(jù)庫饭聚;
- DomainEventPublishingAdapter:實現(xiàn)DomainEventPublisher接口并發(fā)布Ticket領(lǐng)域事件忌警;
4.2 Ticket類的結(jié)構(gòu)
該類使用JPA進(jìn)行持久化,并映射到TICKETS表秒梳。
4.3 Ticket聚合的行為
- create():創(chuàng)建Ticket的工廠方法法绵;
- accept():餐館已接收訂單;
- preparing():餐館已開始準(zhǔn)備訂單端幼,意味著訂單無法再更改或取消礼烈;
- readyForPickup():訂單可以派送;
4.4 KitchenService的領(lǐng)域服務(wù)
KitchenService由服務(wù)入站適配器調(diào)用婆跑;定義了用于更改訂單狀態(tài)的各種方法(如accept()此熬、reject()、preparing()等);每個方法加載指定的聚合犀忱,在聚合根上調(diào)用相應(yīng)的方法募谎,并發(fā)布領(lǐng)域事件;如下accept()方法所示:
accept()方法的兩個參數(shù):
- orderId:要接受訂單的ID阴汇;
- readyBy:訂單可被派送的預(yù)計時間数冬;
4.5 KitchenServiceCommandHandler類
KitchenServiceCommandHandler類是一個適配器,負(fù)責(zé)處理Order Service實現(xiàn)的各種Saga發(fā)送的命令式消息搀庶;
5. Order Service的業(yè)務(wù)邏輯
5.1 Order Service的設(shè)計
- 幾個入站適配器:
- REST API:供消費(fèi)者利用用戶界面調(diào)用的REST API拐纱;它調(diào)用OrderService來創(chuàng)建和更新Order;
- OrderEventConsumer:訂閱Restaurant Service發(fā)布的活動哥倔;它調(diào)用OrderService來創(chuàng)建和更新其Restaurant副本秸架;
- OrderCommandHandlers:由Saga調(diào)用的基于異步請求 / 相應(yīng)的API;它調(diào)用OrderService來更新Order咆蒿;
- SagaReplyAdapter:訂閱Saga回復(fù)通道并調(diào)用Saga东抹;
- 一些出站適配器:
- DB Adapter:實現(xiàn)OrderRepository接口并訪問Order Service的數(shù)據(jù)庫;
- DomainEventPublishingAdapter:實現(xiàn)DomainEventPublisher接口并發(fā)布Order領(lǐng)域事件沃测;
- OutboundCommandMessageAdapter:實現(xiàn)CommandPublisher接口并向Saga參與方發(fā)送命令式消息缭黔;
5.2 Order聚合的結(jié)構(gòu)
Order類是Order聚合的根;Order聚合還包括了值對象蒂破,如:OrderLineItem馏谨、DeliveryInfo和PaymentInfo。
Order類和它的字段:
此類使用JPA持久化寞蚌,并映射到ORDERS表田巴。
5.3 Order聚合狀態(tài)機(jī)
為了創(chuàng)建或更新訂單,Order Service必須使用Saga與其他服務(wù)協(xié)作挟秤。
創(chuàng)建Order過程中調(diào)用的Order方法:
更新Order需要調(diào)用的方法:
5.4 OrderService類
該類定義了用于創(chuàng)建和更新Orders的方法。
6. 微服務(wù)與單體應(yīng)用程序的業(yè)務(wù)邏輯異同點(diǎn)
-
相同點(diǎn):
- 由諸如服務(wù)抄伍、JPA支持的實體類和存儲庫等這樣的類組成艘刚;
-
不同點(diǎn):
- 領(lǐng)域模型被組織為一組DDD聚合,在其上可以施加各種約束截珍;
- 與傳統(tǒng)的對象模型不同攀甚,不同聚合中的類之間的引用是基于主鍵而不是對象引用;
- 事務(wù)只能創(chuàng)建或更新單個聚合岗喉;聚合在狀態(tài)發(fā)生變化時會發(fā)布領(lǐng)域事件秋度;
- 服務(wù)通常使用Saga來維護(hù)多個服務(wù)之間的數(shù)據(jù)一致性;
7. 本章小結(jié)
- 事務(wù)腳本模式通常是實現(xiàn)簡單業(yè)務(wù)的好辦法钱床。但在實現(xiàn)復(fù)雜的業(yè)務(wù)邏輯時荚斯,應(yīng)該考慮使用面向?qū)ο蟮念I(lǐng)域模型模式;
- 設(shè)計服務(wù)的業(yè)務(wù)邏輯的好辦法是使用DDD聚合。DDD聚合很有用事期,因為它們把領(lǐng)域模型模塊化滥壕,消除了服務(wù)之間對象的直接引用,并確保每個ACID事務(wù)都在服務(wù)內(nèi)兽泣;
- 創(chuàng)建或更新聚合時應(yīng)發(fā)布領(lǐng)域事件绎橘。領(lǐng)域事件具有廣泛的用途。第4章討論了如何實現(xiàn)協(xié)同式Saga唠倦。第7章中將討論如何使用領(lǐng)域事件來更新從其他服務(wù)復(fù)制來的數(shù)據(jù)称鳞。領(lǐng)域事件的訂閱者還可以通知用戶和其他應(yīng)用程序,并將WebSocket消息發(fā)布到用戶的瀏覽器稠鼻。