Axon框架指南 - Baeldung
在本文中,我們將介紹Axon以及它如何幫助我們實現具有CQRS(Command Query Responsibility Segregation)和Event Sourcing的應用程式。
在本指南中,將使用Axon Framework和Axon Server。前者將包含我們的實現,後者將是我們專用的事件儲存和訊息路由解決方案。
我們將要構建的示例應用程式專注於Order域。為此,我們將利用Axon為我們提供的CQRS和Event Sourcing構建模組。
請注意,很多共享概念都來自DDD,這超出了本文的範圍。
Maven依賴
我們將建立一個Axon / Spring Boot應用程式。因此,我們需要將最新的axon-spring-boot-starter依賴項新增到我們的pom.xml中,以及用於測試的axon-test依賴項:
<dependency> <groupId>org.axonframework</groupId> <artifactId>axon-spring-boot-starter</artifactId> <version>4.1.2</version> </dependency> <dependency> <groupId>org.axonframework</groupId> <artifactId>axon-test</artifactId> <version>4.1.2</version> <scope>test</scope> </dependency> |
Axon Server
我們將使用Axon Server作為我們的Event Store和我們的專用命令,事件和查詢路由解決方案。
作為事件儲存,它為我們提供了儲存事件時所需的理想特性。該文章提供了背景為什麼這是可取的。
作為訊息路由解決方案,它為我們提供了將多個例項連線在一起的選項,而無需專注於配置RabbitMQ或Kafka主題以共享和分發訊息。
Axon Server可以在這裡下載。由於它是一個簡單的JAR檔案,以下操作足以啟動它:
java -jar axonserver.jar
這將啟動一個可透過localhost訪問的Axon Server例項:8024。端點提供已連線應用程式及其可以處理的訊息的概述,以及Axon Server中包含的事件儲存的查詢機制。
Axon Server的預設配置與axon-spring-boot-starter依賴關係將確保我們的Order服務將自動連線到它。
訂單服務API - 命令
我們將以CQRS為基礎設定訂單服務。因此,我們將強調流經我們應用程式的訊息。
首先,我們將定義命令,即意圖的表達。Order服務能夠處理三種不同型別的操作:
- 下新訂單
- 確認訂單
- 發貨訂單
當然,我們的域可以處理三個命令訊息 - PlaceOrderCommand,ConfirmOrderCommand和ShipOrderCommand:
public class PlaceOrderCommand { @TargetAggregateIdentifier private final String orderId; private final String product; // constructor, getters, equals/hashCode and toString } public class ConfirmOrderCommand { @TargetAggregateIdentifier private final String orderId; // constructor, getters, equals/hashCode and toString } public class ShipOrderCommand { @TargetAggregateIdentifier private final String orderId; // constructor, getters, equals/hashCode and toString } |
該TargetAggregateIdentifier註解告訴軸突的註釋欄位是一個給定的聚合ID,以該命令應該有針對性。 我們將在本文後面簡要介紹聚合。
另請注意,我們將命令中的欄位標記為 final。 這是故意的,因為任何訊息實現都是不可變的最佳實踐。
訂單服務API - 事件
我們的聚合將處理這些命令,因為它負責決定是否可以下達,確認或傳送訂單。
它將透過釋出活動通知其決定的其餘部分。我們將有三種型別的事件 - OrderPlacedEvent,OrderConfirmedEvent和OrderShippedEvent:
public class OrderPlacedEvent { private final String orderId; private final String product; // default constructor, getters, equals/hashCode and toString } public class OrderConfirmedEvent { private final String orderId; // default constructor, getters, equals/hashCode and toString } public class OrderShippedEvent { private final String orderId; // default constructor, getters, equals/hashCode and toString } |
命令模型 - 訂單聚合
現在我們已經根據命令和事件建模了我們的核心API,我們可以開始建立命令模型。
由於我們的領域專注於處理訂單, 我們將建立一個OrderAggregate作為我們的命令模型的中心。
聚合類,建立我們的基本聚合類:
@Aggregate public class OrderAggregate { @AggregateIdentifier private String orderId; private boolean orderConfirmed; @CommandHandler public OrderAggregate(PlaceOrderCommand command) { AggregateLifecycle.apply(new OrderPlacedEvent(command.getOrderId(), command.getProduct())); } @EventSourcingHandler public void on(OrderPlacedEvent event) { this.orderId = event.getOrderId(); orderConfirmed = false; } protected OrderAggregate() { } } |
使用@Aggregate註釋標記這個類作為一個聚合體。它將通知框架需要為此OrderAggregate例項化所需的CQRS和Event Sourcing特定構建塊。
由於聚合將處理針對特定聚合例項的命令,因此我們需要使用AggregateIdentifier註釋指定識別符號。
在OrderAggregate '命令處理建構函式'中處理PlaceOrderCommand時,我們的聚合將開始其生命週期。為了告訴框架使用指定函式處理命令,我們將新增CommandHandler註釋。
處理PlaceOrderCommand時,它將透過釋出OrderPlacedEvent通知應用程式的其餘部分已下達訂單。要從聚合中釋出事件,我們將使用 AggregateLifecycle application(Object ...)。
從這一點開始,我們實際上可以開始將Event Sourcing作為從事件流中重新建立聚合例項的驅動力。
我們從“聚合建立事件”開始,即OrderPlacedEvent,它在EventSourcingHandler註釋函式中處理,以設定Order聚合的orderId和orderConfirmed狀態。
另請注意,為了能夠根據事件來源聚合,Axon需要一個預設建構函式。
聚合命令處理程式
現在我們有了基本聚合,我們可以開始實現剩餘的命令處理程式:
@CommandHandler public void handle(ConfirmOrderCommand command) { apply(new OrderConfirmedEvent(orderId)); } @CommandHandler public void handle(ShipOrderCommand command) { if (!orderConfirmed) { throw new UnconfirmedOrderException(); } apply(new OrderShippedEvent(orderId)); } @EventSourcingHandler public void on(OrderConfirmedEvent event) { orderConfirmed = true; } |
我們已經定義訂單隻有在確認後才能發貨。因此,如果不是這種情況,我們將丟擲UnconfirmedOrderException。
這表明OrderConfirmedEvent採購處理程式需要將Order聚合的orderConfirmed狀態更新為true。
測試命令模型
首先,我們需要建立一個為OrderAggregate測試的配置FixtureConfiguration :
private FixtureConfiguration<OrderAggregate> fixture; @Before public void setUp() { fixture = new AggregateTestFixture<>(OrderAggregate.class); } |
第一個測試用例應該涵蓋最簡單的情況。當聚合處理 PlaceOrderCommand時,它應該生成一個 OrderPlacedEvent:
String orderId = UUID.randomUUID().toString(); String product = "Deluxe Chair"; fixture.givenNoPriorActivity() .when(new PlaceOrderCommand(orderId, product)) .expectEvents(new OrderPlacedEvent(orderId, product)); |
接下來,我們可以測試只有在確認後能夠傳送訂單的決策邏輯。因此,我們有兩個場景 - 一個是我們期望異常的場景,另一個是我們期望 OrderShippedEvent的場景。
讓我們看看第一個場景,我們期待一個異常:
String orderId = UUID.randomUUID().toString(); String product = "Deluxe Chair"; fixture.given(new OrderPlacedEvent(orderId, product)) .when(new ShipOrderCommand(orderId)) .expectException(IllegalStateException.class); |
現在是第二種情況,我們期待OrderShippedEvent:
String orderId = UUID.randomUUID().toString(); String product = "Deluxe Chair"; fixture.given(new OrderPlacedEvent(orderId, product), new OrderConfirmedEvent(orderId)) .when(new ShipOrderCommand(orderId)) .expectEvents(new OrderShippedEvent(orderId)); |
查詢模型 - 事件處理程式
到目前為止,我們已經使用命令和事件建立了我們的核心API,並且我們擁有CQRS Order服務的Command模型,Order aggregate。
接下來, 我們可以開始考慮我們的應用程式應該服務的查詢模型之一。
其中一個模型是OrderedProducts:
public class OrderedProduct { private final String orderId; private final String product; private OrderStatus orderStatus; public OrderedProduct(String orderId, String product) { this.orderId = orderId; this.product = product; orderStatus = OrderStatus.PLACED; } public void setOrderConfirmed() { this.orderStatus = OrderStatus.CONFIRMED; } public void setOrderShipped() { this.orderStatus = OrderStatus.SHIPPED; } // getters, equals/hashCode and toString functions } public enum OrderStatus { PLACED, CONFIRMED, SHIPPED } |
我們將根據透過系統傳播的事件更新此模型。用於更新模型的Spring Service bean可以解決這個問題:
@Service public class OrderedProductsEventHandler { private final Map<String, OrderedProduct> orderedProducts = new HashMap<>(); @EventHandler public void on(OrderPlacedEvent event) { String orderId = event.getOrderId(); orderedProducts.put(orderId, new OrderedProduct(orderId, event.getProduct())); } // Event Handlers for OrderConfirmedEvent and OrderShippedEvent... } |
由於我們已經使用axon-spring-boot-starter依賴來啟動我們的Axon應用程式,因此框架將自動掃描所有bean以查詢現有的訊息處理函式。
由於 OrderedProductsEventHandler具有用於儲存OrderedProduct並更新它的EventHandler註釋函式,因此該bean將被框架註冊為應該接收事件而不需要我們任何配置的類。
查詢模型 - 查詢處理程式
接下來,要查詢此模型,例如,要檢索所有已訂購的產品,我們應首先向我們的核心API引入一條Query訊息:
public class FindAllOrderedProductsQuery { }
其次,我們必須更新OrderedProductsEventHandler才能處理FindAllOrderedProductsQuery:
@QueryHandler public List<OrderedProduct> handle(FindAllOrderedProductsQuery query) { return new ArrayList<>(orderedProducts.values()); } |
該QueryHandler註釋功能將處理FindAllOrderedProductsQuery並設定為返回一個List<OrderedProduct>,類似“find all”查詢。
把所有東西放在一起
我們透過命令,事件和查詢充實了我們的核心API,並透過OrderAggregate和OrderedProducts模型設定了我們的命令和查詢模型。
接下來是繫結我們基礎設施的鬆散端。當我們使用axon-spring-boot-starter時,它會自動設定許多所需的配置。
首先,由於我們想要為我們的聚合利用事件採購,我們需要一個EventStore。我們在第三步中啟動的Axon Server將填補這個漏洞。
其次,我們需要一種機制來儲存我們的OrderedProduct查詢模型。對於此示例,我們可以新增h2作為記憶體資料庫和spring-boot-starter-data-jpa以便於使用:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> |
設定REST端點
接下來,我們需要能夠訪問我們的應用程式,我們將透過新增spring-boot-starter-web依賴關係來利用REST端點:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> |
從我們的REST端點,我們可以開始排程命令和查詢:
@RestController public class OrderRestEndpoint { private final CommandGateway commandGateway; private final QueryGateway queryGateway; // Autowiring constructor and POST/GET endpoints } |
該CommandGateway被用作機制傳送我們的命令訊息,以及QueryGateway,然後傳送查詢訊息,
與 CommandBus和QueryBus相比,該閘道器提供了更簡單,更直接的API 。
從這裡開始,我們的OrderRestEndpoint應該有一個POST端點來放置,確認和傳送訂單:
@PostMapping("/ship-order") public void shipOrder() { String orderId = UUID.randomUUID().toString(); commandGateway.send(new PlaceOrderCommand(orderId, "Deluxe Chair")); commandGateway.send(new ConfirmOrderCommand(orderId)); commandGateway.send(new ShipOrderCommand(orderId)); } |
這使我們的CQRS應用程式的命令端更加完整。
現在,剩下的就是一個GET端點來查詢所有OrderedProducts:
@GetMapping("/all-orders") public List<OrderedProduct> findAllOrderedProducts() { return queryGateway.query(new FindAllOrderedProductsQuery(), ResponseTypes.multipleInstancesOf(OrderedProduct.class)).join(); } |
在GET端點中,我們利用QueryGateway來分派點對點查詢。於是,我們建立一個預設的 FindAllOrderedProductsQuery,但我們還需要指定預期的返回型別。
由於我們期望返回多個OrderedProduct例項,因此我們利用靜態ResponseTypes#multipleInstancesOf(Class)函式。有了這個,我們為訂單服務的查詢方面提供了一個基本入口。
我們完成了設定,現在我們可以在啟動OrderApplication後透過REST控制器傳送一些命令和查詢 。
POST到端點/發貨訂單將例項化一個OrderAggregate,它將釋出事件,這反過來將儲存/更新我們的OrderedProducts。來自/ all-orders 端點的GET 將釋出一個查詢訊息,該訊息將由OrderedProductsEventHandler處理,該訊息將返回所有現有的OrderedProducts。
結論
在本文中,我們介紹了Axon Framework作為構建應用程式的強大基礎,充分利用了CQRS和Event Sourcing的優勢。
我們使用框架實現了一個簡單的Order服務,以展示如何在實踐中構建這樣的應用程式。
最後,Axon Server構成了我們的事件儲存和訊息路由機制。
可以在GitHub上找到所有這些示例和程式碼片段的實現。
如果您有任何其他問題,請檢視Axon Framework使用者組。
相關文章
- SOLID原則的堅實指南| BaeldungSolid
- CQRS架構和Axon框架入門實踐架構框架
- Axon框架快速入門和DDD專案實踐框架
- axon框架創始人談微服務與事件驅動框架微服務事件
- 基於Axon框架使用Kotlin編寫的ES銀行案例框架Kotlin
- 多版本SDK並行管理工具:SDKMAN指南 - Baeldung並行
- 在Axon框架中揭開跟蹤事件處理器的神秘面紗框架事件
- 框架庫(.NET 指南)框架
- Java ORM 框架指南JavaORM框架
- 使用async-profiler進行JVM記憶體效能微調的指南 | BaeldungJVM記憶體
- Monkey框架使用指南框架
- 使用Axon重播投射事件 - codecentric AG Blog事件
- Netflix Mantis簡介 - Baeldung
- 輕量級orm框架——gzero指南ORM框架
- 自動化測試框架指南框架
- Spring Boot Reactor Netty配置 | BaeldungSpring BootReactNetty
- 如何找到JAVA_HOME | BaeldungJava
- Java IdentityHashMap類的用法 | baeldungJavaIDEHashMap
- 包、元包和框架(.NET Core 指南)框架
- 美團小程式框架mpvue蹲坑指南框架Vue
- Spring Boot面試問題| BaeldungSpring Boot面試
- Spring Cloud Gateway WebFilter工廠 | BaeldungSpringCloudGatewayWebFilter
- Evrete 規則引擎簡介 | baeldungVR
- 微服務中的Saga模式 - baeldung微服務模式
- Java AES加密和解密教程 - BaeldungJava加密解密
- Vue框架TypeScript裝飾器使用指南Vue框架TypeScript
- Agent 智慧體開發框架選型指南智慧體框架
- (譯)使用Spring Boot和Axon實現CQRS&Event SourcingSpring Boot
- 在Spring Boot設定Swagger 2 - BaeldungSpring BootSwagger
- GraphQL SPQR和Spring Boot入門 | baeldungSpring Boot
- Docker, Dockerfile, 和Docker Compose區別 | BaeldungDocker
- 阿里巴巴哨兵Sentinel簡介 | Baeldung阿里
- 如何在SpringBoot中設定TLS? |BaeldungSpring BootTLS
- Python網路框架Django和Scrapy安裝指南Python框架Django
- 高效能非同步框架Celery入坑指南非同步框架
- HamronyOS 自動化測試框架使用指南框架
- 狀態管理框架開發不完全指南框架
- 深度學習框架新手快速上手指南深度學習框架