Axon 3.0.x 框架簡介官方文檔


因為需要用到,但是在網上對應的資料實在是很少,只有迎着頭皮看官網文檔並配合翻譯器。如有誤導多多包涵。

Axon

什么是 Axon

Axon Framework 通過支持開發人員應用命令查詢責任隔離(CQRS)架構模式來幫助構建可擴展和可維護的應用程序。它通過提供最重要的構建塊(例如聚合,存儲庫和事件總線(事件的分發機制))的實現來實現。此外 Axon 提供注釋支持,它使您可以構建聚合和事件偵聽器,而無需將代碼與 Axon 特定的邏輯綁定在一起。這使您可以專注於業務邏輯而不是管道,並可以使您的代碼更易於獨立測試。

並非每個應用程序都會從 Axon 中受益。不期望擴展的簡單 CRUD 應用程序可能不會從 CQRS 或 Axon 中受益。然而,有各種各樣的應用程序確實受益於 Axon。
可能受益於 CQRS 和 Axon 的應用:

  1. (系統功能需要頻繁迭代新功能)應用程序很可能在很長一段時間內使用新功能進行擴展。例如,在線商店可能從訂單模塊進度的系統開始。在稍后階段,這可以通過庫存信息進行擴展,以確保庫存在出售時得到更新。甚至在以后,會計可以要求記錄銷售的財務統計,等等。雖然很難預測軟件項目在未來將如何發展,但大多數這類應用程序都是這樣的。
  2. (頻繁讀寫的應用)應用程序具有高的讀寫比。這意味着數據只寫幾次,並多次讀。由於查詢的數據源與用於命令驗證的數據源不同,因此可以優化這些數據源以實現快速查詢。重復數據不再是問題,因為數據更改時會發布事件。
  3. 應用程序以多種格式顯示數據。現在,許多應用程序在網頁上顯示信息時不會停止。例如,一些應用程序每月發送電子郵件,通知用戶可能發生的與其相關的更改。搜索引擎是另一個例子。它們使用的數據與應用程序使用的數據相同,但以一種為快速搜索而優化的方式。報表工具將信息聚合到顯示數據隨時間演變的報表中。同樣,這也是同一數據的不同格式。使用 Axon,可以在實時或計划的基礎上獨立地更新每個數據源。
  4. 當應用程序明顯地將組件與不同的對象分開時,它也可以從 Axon 中受益。這種應用程序的一個例子是在線商店。員工將在網站上更新產品信息和可用性,而客戶則會發出訂單並查詢其訂單狀態。使用 Axon,這些組件可以部署在不同的機器上,並使用不同的策略進行縮放。它們使用事件保持最新狀態,無論部署在哪台機器上,Axon 都會將這些事件分派給所有訂閱的組件。
  5. 與其他應用程序集成可能是一項繁瑣的工作。使用命令和事件嚴格定義應用程序的 API 可以使其更容易與外部應用程序集成。任何應用程序都可以發送命令或偵聽應用程序生成的事件。

架構概述

CQRS 本身是非常簡單的模式。它只規定處理命令的應用程序的組件應該與處理查詢的組件分離。雖然這種分離本身非常簡單,但是當與其它模式結合時,它提供了許多非常強大的特征。

axon 提供構建塊,使得能夠更容易地實現可以與 CQRS 組合使用的不同模式。下圖顯示了基於CQRS的事件驅動架構的擴展布局的示例。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ae30iIc6-1570778817749)在這里插入圖片描述
左側顯示的 UI 組件以兩種方式與應用程序的其余部分交互:它向應用程序發送命令 Command(在頂部顯示),並查詢應用程序以獲取信息(在底部顯示)。

命令通常由簡單和直接的對象表示,對象包含命令處理程序執行該命令所需的所有數據。命令以其名稱表示其意圖。在 Java 術語中,這意味着使用類名稱來找出需要完成的操作,並且命令的字段提供執行該操作所需的信息。命令總線接收命令並將它們路由到命令處理程序。每個命令處理程序響應特定類型的命令並基於命令的內容執行邏輯。但是,在某些情況下,您還希望執行邏輯,而不管實際的命令類型,例如驗證、日志記錄或授權。命令處理程序從存儲庫中檢索域對象(聚合),並對其執行方法以更改其狀態。這些聚合通常包含實際的業務邏輯,因此負責保護它們自己的不變量。

聚合的狀態變化導致生成域事件。域事件和聚合都形成域模型。存儲庫負責提供對聚合的訪問。通常,這些存儲庫被優化用於僅通過其唯一標識符來查找聚合。一些存儲庫將存儲聚合本身的狀態(例如,使用對象關系映射),而其他存儲庫存儲聚合已在事件存儲中經歷的狀態更改。存儲庫還負責持久存儲在其備份存儲中對聚合進行的更改。

Axon 為持久聚合的直接方式(例如使用對象關系映射)和事件來源提供支持。事件總線將事件分派給所有感興趣的事件偵聽器。這可以同步地或異步地完成。異步事件調度允許命令執行返回並將控制移交給用戶,而在后台調度和處理事件。不必等待事件處理才能完成應用程序的響應。另一方面,同步事件處理更簡單,是明智的默認處理。默認情況下,同步處理將處理同樣處理該命令的同一事務中的事件偵聽器。事件偵聽器接收事件並處理它們。一些處理程序將更新用於查詢的數據源,而另一些處理程序將消息發送到外部系統。正如您可能注意到的,命令處理程序完全不知道它們所做的更改感興趣的組件。這意味着將應用擴展到新的功能是非常非侵入性的。您只需添加另一個事件偵聽器。這些事件將應用程序中的所有組件松散地耦合在一起。在某些情況下,事件處理需要向應用程序發送新命令。

這是當接收到訂單時的示例。這可能意味着客戶的賬戶應借記購買的金額,並且必須告知裝運准備裝運所購買的貨物。在許多應用中,邏輯會變得更加復雜:如果客戶沒有及時付款怎么辦?您將立即發貨,還是先等待付款?Saga 是負責管理這些復雜業務交易的 CQRS 概念。用戶接口和數據源之間的薄數據層提供了與所使用的實際查詢實現的明確定義的接口。此數據層通常將只讀DTO 返回到包含查詢結果的對象。這些 DTO 的內容通常由用戶接口的需要來驅動。在大多數情況下,它們直接映射到 UI 中的特定視圖(也稱為表視圖)。Axon 不能為該部分的應用提供任何構建塊。主要原因是這非常簡單,與分層體系結構不同。

模塊結構

AxonFramework 由多個模塊組成,這些模塊針對 CQRS 的特定問題區域。根據項目的確切需求,您需要包含一個或多個這些模塊。

  1. core: 模塊包含 Axon 的核心組件。如果使用單節點設置,則此模塊可能會提供所需的所有組件。所有其他 Axon 模塊都依賴於這個模塊,因此它必須始終在類路徑上可用。
  2. test: 測試模塊包含測試工具,可以用來測試基於 Axon 的組件,例如命令處理程序、Aggregates 和 Sagas。通常在運行時不需要這個模塊,只需要在測試期間添加到類路徑中。
  3. Distributed: 分布式 CommandBus 模塊包含可用於在多個節點上分發命令的實現。它附帶了用於連接這些節點的JGroups 和 SpringCloud 連接器。
  4. AMQP: AMQP 模塊提供的組件允許您使用基於 AMQP 的消息代理作為分發機制來構建 EventBus。這允許保證交付,即使事件處理程序節點暫時不可用。
  5. Spring: Spring 模塊允許在 SpringApplication 上下文中配置 Axon 組件.它還提供了許多特定於 Spring 框架的構建塊實現,例如在 Spring 消息通道上發布和檢索 Axon 事件的適配器。
  6. MongoDB: 是一個基於文檔的 NoSQL 數據庫.Mongo 模塊提供事件和 Saga Store 實現,這些實現將事件流和 sagas 存儲在 MongoDB 數據庫中。 幾個 Axon Framework 組件提供監控信息。度量模塊提供了基於 Codehale 的基本實現來收集監控信息。

Spring 支持

Axon Framework 為 Spring 提供了廣泛的支持,但不需要您使用 Spring 即可使用 Axon。 所有組件都可以通過編程方式配置,並且不需要在類路徑上使用 Spring。 但是,如果確實使用 Spring,則通過使用 Spring 的注釋支持可以簡化許多配置。

消息傳遞

Axon 的核心概念之一是消息傳遞。組件之間的所有通信都是使用消息對象完成的。這為這些組件提供了必要的位置透明性,以便在必要時能夠擴展和分發這些組件。盡管所有這些消息都實現了消息接口,但不同類型的消息與如何處理它們之間有着明顯的區別。所有消息都包含有效負載、元數據和唯一標識符。消息的有效負載是對消息含義的功能描述。這個對象的類名和它所攜帶的數據的組合,描述了應用程序對消息的意義。元數據允許您描述發送消息的上下文。例如,您可以存儲跟蹤信息,以允許跟蹤郵件的來源或原因。您還可以存儲信息來描述執行命令的安全上下文。

Command

Command 描述更改應用程序狀態的意圖。它們被實現為(最好是只讀) POJO,它們使用一個 CommandMessage 實現包起來。 命令總是有一個目的地。雖然發送方不關心哪個組件處理命令或該組件所在的位置,但了解它的結果可能會很有趣。這就是為什么通過命令總線發送的命令消息允許返回結果的原因。

Event

Event 是描述應用程序中發生的事情的對象。事件的一個典型來源是聚合。當聚合中發生重要事件時,它將引發一個事件。在Axon 框架中,事件可以是任何對象。我們非常鼓勵您確保所有事件都是可序列化的。當事件被分派時,Axon 會將它們封裝在EventMessage 中。實際使用的消息類型取決於事件的起源。當事件由聚合引發時,它被包裝在 DomainEventMessage (擴展 EventMessage)中。所有其他事件都封裝在 EventMessage 中。除了通用消息屬性(如唯一標識符)外,EventMessage 還包含時間戳。DomainEventMessage 還包含引發事件的聚合的類型和標識符。它還包含聚合的事件流中事件的序列號,這允許復制事件的順序。

Unit of Work

工作單元是 Axon 框架中的一個重要概念,盡管在大多數情況下,您不太可能直接與它交互。消息的處理被看作是一個單一的單元。工作單位的目的是協調在處理消息(命令或事件)期間所執行的行動。組件可以注冊在工作單元的每個階段執行的操作。

例如 onPrepareCommit 或 onCleanup。你不太可能需要直接訪問工作單元。它主要由 Axon 提供的構建塊使用。如果您確實需要訪問它,無論出於什么原因,有幾種方法可以獲得它。

  1. Handler 通過 Handle 方法中的參數接收工作單元。如果使用注釋支持,則可以將 UnitOfWork 類型的參數添加到帶注釋的方法中。
  2. 在其他位置,可以通過調用 CurrentUnitOfWork.get() 檢索綁定到當前線程的工作單元。注意,如果沒有綁定到當前線程的工作單元,此方法將引發異常。使用 CurrentUnitOfWork.isStarted() 查找是否可用。要求訪問當前工作單元的原因之一是,附加在消息處理過程中需要多次重復使用的資源,或者在工作單元完成時是否需要清理創建的資源。在這種情況下,unitOfWork.getOrComputeResource() 和生命周期回調方法(如 onRollback()、postCommit() 和onCleanup() 允許您注冊資源並聲明在此工作單元處理過程中要采取的操作。

請注意,工作單元只是變更的緩沖,而不是事務的替代。雖然所有分階段的更改都是在提交工作單元時才提交的,但它的提交並不是原子的。這意味着,當提交失敗時,一些更改可能會持久化,而其他更改則不會。最佳實踐規定,命令不應包含多個操作。如果你堅持這種做法,一個工作單位將包含一個單一的行動,使它安全地使用原樣。如果工作單元中有更多的操作,那么可以考慮將事務附加到 Work 的提交。 使用 unitOfWork.onCommit(…) 注冊在提交工作單元時需要執行的操作。

處理程序可能會由於處理消息而引發異常。默認情況下,未經檢查的異常將導致 UnitOfWork 回滾所有更改。Axon提供了一些開箱即用的回滾策略:

  1. RollbackConfigurationType.NEVER 將始終提交工作單元
  2. RollbackConfigurationType.ANY_THROWABLE 發生異常時將始終回滾
  3. RollbackConfigurationType.UNCHECKED_EXCEPTIONS 將回滾錯誤和運行時異常
  4. RollbackConfigurationType.RUNTIME_EXCEPTION 將回滾運行時異常但是不包括 Error

當使用 Axon 組件處理消息時,將自動管理工作單元的生命周期。如果選擇不使用這些組件,而是自己實現處理,則需要以編程方式啟動和提交(或回滾)一個工作單元。在大多數情況下,DefaultUnitOfWork 將為您提供所需的功能。它期望在單個線程中進行處理。要在工作單元的上下文中執行任務,只需在新的 DefaultUnitOfWork 上調用UnitOfWork.ExecutWithResult(可調用)。工作單元將在任務完成時啟動和提交,或者在任務失敗時回滾。如果需要更多的控制,也可以選擇手動啟動、提交或回滾工作單元。示例如下:

UnitOfWork uow = DefaultUnitOfWork.startAndGet(message);
// then, either use the autocommit approach:
uow.executeWithResult(() -> ... logic here);

// or manually commit or rollback:
try {
    // business logic comes here
    uow.commit();
} catch (Exception e) {
    uow.rollback(e);
    // maybe rethrow...
}

一個工作單元包含幾個階段。 每次進入另一個階段時,都會通知 UnitOfWork 偵聽器。

  1. Active phase: 工作單位已啟動。工作單位通常在此階段(通過 CurrentUnitOfWork.set(UNITOFWork))注冊。隨后,該消息通常由這個階段中的消息處理器處理。
  2. Commit phase: 在完成消息處理之后,但在提交工作單元之前,將調用 onPrepareCommit 偵聽器。如果工作單元綁定到事務,則調用 onCommit 偵聽器來提交任何支持事務。當提交成功時,將調用 postCommit 偵聽器。如果提交或之前的任何步驟失敗,則調用 onRollback 偵聽器。如果可用,消息處理程序結果包含在工作單元的 ExecutionResult中。
  3. Cleanup phase: 這是該單位所持有的任何資源(如鎖)將被釋放的階段。如果嵌套了多個工作單位,則清理階段將被推遲,直到工作的外部單元准備好清理。

消息處理過程可以被認為是原子過程;它要么完全被處理,要么根本不被處理。Axon 框架使用工作單位跟蹤消息處理程序執行的操作。在處理程序完成后,Axon 將嘗試提交與工作單位注冊的操作。可以將事務綁定到工作單位。許多組件(例如CommandBus 實現和所有異步處理事件處理器)允許您配置事務管理器。然后,該事務管理器將被用於創建與用於管理消息處理的工作單元綁定的事務。當應用程序組件在消息處理的不同階段需要資源時,例如數據庫連接或 EntityManager,這些資源可以附加到工作單位。unitOfWork.GetResources() 方法允許您訪問連接到當前工作單元的資源。在工作單元上直接提供了幾種輔助方法,以便更容易地與資源一起工作。當嵌套的工作單位需要能夠訪問資源時,建議將其注冊到可以使用unitOfWork.root() 訪問的工作的根單元上。如果工作單位是聚合根,它將簡單地返回自己。

快速開始

下面就讓我們快速開始吧,首先我們可以從 Maven、Gradle 中來獲取依賴.

<!-- Maven 依賴 -->
<dependency>
    <groupId>org.axonframework</groupId>
    <artifactId>axon-core</artifactId>
    <version>${axon.version}</version>
</dependency>

AxonFramework 是在 Java 8 上構建和測試的。由於 Axon 本身不創建任何連接或線程,所以在 ApplicationServer 上運行是安全的。Axon 通過使用執行器抽象所有異步行為,這意味着您可以輕松地傳遞一個容器托管線程池。如果您不使用完整的 ApplicationServer (例如 Tomcat、Jetty 或獨立應用程序),您可以使用 Executors 類或SpringFramework 來創建和配置線程池。

以上一個簡單的 axon 框架環境就搭建好了.是不是很簡單.

Command Model

在基於 CQRS 的應用程序中,域模型可以是一種非常強大的機制,可以利用狀態更改的驗證和執行所涉及的復雜性。雖然典型的領域模型有大量的構建塊,但其中一個模塊在 CQRS 中的命令處理中起着主導作用:聚合。應用程序中的狀態更改以命令開始。命令是表示意圖(它描述了您想要做的事情)以及基於該意圖進行操作所需的信息的組合。命令模型用於處理傳入的命令,驗證它並定義結果。在此模型中,命令處理程序負責處理特定類型的命令,並根據其中包含的信息采取行動。

Aggregate

聚合是一個實體或一組實體,總是保持一致的狀態。聚合根是聚合樹頂部的對象,負責保持這種一致的狀態。這使得聚合成為在任何基於 CQRS 的應用程序中實現命令模型的主要構建塊。
例如,“Contact(聯系人)”聚合可以包含兩個實體:聯系人和地址。為了使整個聚合保持一致狀態,應通過聯系人實體為聯系人添加地址。在這種情況下,聯系人實體是指定的聚合根。

在 Axon 中,聚合由聚合標識符標識。這可能是任何對象,但是對於標識符的良好實現有一些准則。標識符必須:

  1. 實現 eques 和 hashCode,以確保與其他實例進行良好的平等比較
  2. 實現一個提供一致結果的 toString 方法
  3. 實現 Serializable 成為可序列化的

當聚合使用不兼容的標識符時,測試模塊將驗證這些條件並通過測試。字符串、UUID 和數字類型的標識符始終適用。不要使用原始類型作為標識符,因為它們不允許延遲初始化。在某些情況下,Axon 可能會錯誤地將圖元的默認值假定為標識符的值。

聚合總是通過一個名為聚合根的實體訪問。通常,該實體的名稱與聚合的名稱完全相同。例如,訂單聚合可能由一個 Order 實體組成,該實體引用多個 Orderline 實體。有序與有序相結合,形成集合。聚合是一個常規對象,它包含狀態和改變該狀態的方法。雖然根據 CQRS 原則並不完全正確,但也可以通過訪問器方法公開聚合的狀態。聚合根必須聲明包含聚合標識符的字段。必須最遲在發布第一個事件時初始化此標識符。此標識符字段必須由 @AggregateIdentifier 注釋進行注釋。如果您使用JPA 並在聚合上有 JPA 注釋,Axon 也可以使用 JPA 提供的 @ID 注釋。聚合可以使用 AggregateLifecycle.Apply() 方法注冊事件以供發布。與 EventBus 不同,EventBus 需要將消息包裝在EventMessage 中,Apply() 允許您直接傳遞有效負載對象。

@Entity // 標記聚合是一個 jpa 實體
public class MyAggregate {
    @Id // 使用 JPA @Id 進行注釋時,不需要 @AggregateIdentifier 注釋
    private String id;

    // fields containing state...

    @CommandHandler
    public MyAggregate(CreateMyAggregateCommand command) {
        // ... update state
        apply(new MyAggregateCreatedEvent(...));
    }

    // constructor needed by JPA
    protected MyAggregate() {
    }
}

聚合中的實體可以通過定義 @EventHandler 注釋方法來偵聽聚合發布的事件。這些方法將在 EventMessage 發布時調用(在任何外部處理程序發布之前)。

Event sourced aggregates

除了存儲聚合的當前狀態外,還可以根據過去發布的事件重新構建聚合的狀態。要使此操作正常,所有狀態更改都必須由一個事件表示。對於主要部分,事件源聚合類似於“常規”聚合:它們必須聲明一個標識符,並且可以使用 Apply 方法發布事件。但是,事件源集合中的狀態更改(即字段值的任何更改)必須在 @EventSourcingHandler 注釋的方法中獨占執行。這包括設置聚合標識符。注意,聚合標識符必須在聚合發布的第一個事件的 @EventSourcingHandler 中設置。這通常是創建事件。事件源聚合的聚合根還必須包含無 Arg 構造函數。AxonFramework 使用此構造函數在使用過去的事件初始化聚合實例之前創建一個空聚合實例。未能提供此構造函數將導致加載聚合時出現異常。

public class MyAggregateRoot {

    @AggregateIdentifier
    private String aggregateIdentifier;

    // fields containing state...

    @CommandHandler
    public MyAggregateRoot(CreateMyAggregate cmd) {
        apply(new MyAggregateCreatedEvent(cmd.getId()));
    }

    // constructor needed for reconstruction
    protected MyAggregateRoot() {
    }

    @EventSourcingHandler
    private void handleMyAggregateCreatedEvent(MyAggregateCreatedEvent event) {
        // 確保標識符始終正確初始化
        this.aggregateIdentifier = event.getMyAggregateIdentifier();
        // ... update state
    }
}

在某些情況下,特別是當聚合結構超過了幾個實體時,對在同一聚合的其他實體中發布的事件作出反應是比較干凈的。但是,由於在重構聚合狀態時也會調用事件處理程序方法,因此必須采取特殊的預防措施。可以在事件 SourcingHandler 方法中Apply() 新事件。這使得實體 B 能夠將事件應用於實體 A 所做的事情。當重播歷史事件時,Axon 將忽略 Apply() 調用。請注意,在本例中,內部 Apply() 調用的事件僅在所有實體接收到第一個事件之后才發布到實體。如果需要發布更多事件,則根據應用內部事件后實體的狀態,使用 Apply(.).和 ThenApply(.) 您還可以使用靜態的AggregateLifecycle.isLive() 方法來檢查聚合是否是“live”。基本上,如果一個集合已經完成了歷史事件的重播,它就被認為是實時的。在重播這些事件時,isLive() 將返回 false。使用此 isLive() 方法,您可以執行僅在處理新生成的事件時才應該執行的活動。

Complex Aggregate structures

復雜的業務邏輯通常所需要的不只是僅具有聚合根的聚合所能提供的。在這種情況下,重要的是將復雜性分布在聚合中的多個實體上。在使用事件源時,不僅聚合根需要使用事件來觸發狀態轉換,而且聚合中的每個實體也需要使用事件來觸發狀態轉換。
注意聚合不應公開狀態的規則的一個常見誤解是,任何實體都不應該包含任何屬性訪問器方法。 不是這種情況。 實際上,如果聚合中的實體向同一聚合中的其他實體公開狀態,則聚合可能會受益匪淺。 但是,建議不要將狀態暴露在聚合外部。

Axon 為復雜聚合結構中的事件源提供支持。就像聚合根一樣,實體是簡單的對象。聲明子實體的字段必須使用@AggregateMember 注釋。 此注釋告訴 Axon,帶注釋的字段包含應檢查命令和事件處理程序的類。

當一個實體(包括聚合根)應用一個事件時,它首先由聚合根處理,然后通過所有 @AggregateMember 注釋字段冒泡直至其子實體。包含子實體的字段必須使用 @AggregateMember 注釋。此批注可用於多種字段類型:

  1. 在字段中直接引用的實體類型
  2. 包含 Iterable(包含所有集合,例如 Set,List 等)的內部字段
  3. 在包含 java.util.Map 的字段的值中

Handling commands in an Aggregate

建議直接在包含處理該命令狀態的 Aggregate 中定義命令處理程序,因為命令處理程序不太可能需要該 Aggregate 的狀態來完成其工作。

若要在聚合中定義命令處理程序,只需使用 @CommandHandler 注釋命令處理方法。@CommandHandler 注釋方法的規則與任何處理程序方法相同。然而,命令不僅是由它們的有效負載路由的。命令消息帶有一個名稱,默認為 Command 對象的完全限定類名。

默認情況下,@CommandHandler 注釋方法允許以下參數類型:

  1. 第一參數是命令消息的有效載荷。如果 @CommandHandler 注釋明確定義了處理程序可以處理的命令的名稱,則它也可以是類型消息或 CommandMessage。缺省情況下,命令名稱是命令的有效負載的完全限定的類名稱。
  2. 用 @MetadataValue 注釋的參數將以注釋所示的鍵解析為元數據值。如果需要為 false(默認),則在元數據值不存在時傳遞 NULL。如果需要,則解析器將不匹配,並且在元數據值不存在時阻止該方法被調用。
  3. MetaData 類型的參數將注入 CommandMessage 的整個 MetaData。
  4. 類型為 UnitOfWork 的參數獲取當前注入的工作單位。這使命令處理程序可以注冊要在工作單元的特定階段執行的操作,或者訪問對其注冊的資源。
  5. 類型為 Message 或 CommandMessage 的參數將獲取完整的消息以及有效負載和元數據。如果方法需要幾個元數據字段或包裝的 Message 的其他屬性,則此方法很有用。

為了使 Axon 知道哪個 Aggregate 類型的實例應處理命令消息,必須在 @CommandAggregateIdentifier 處注釋Command 對象中帶有 AggregateIdentifier 的屬性。 注釋可以放在字段或訪問器方法(例如 getter)上。

創建聚合實例的命令無需標識目標聚合標識符,盡管建議也對它們進行注釋。如果您更喜歡使用另一種機制來路由命令,則可以通過提供自定義 CommandTargetResolver 來覆蓋該行為。該類應該根據給定的命令返回聚合標識符和預期版本(如果有的話)。

當 @CommandHandler 注釋放置在聚合 Aggregate 構造函數上時,相應的命令將創建該聚合的新實例並將其添加到存儲庫中。這些命令不需要針對特定的聚合實例。因此,這些命令不需要任何 @TargetAggregateIdentifier 或@TargetAggregateVersion 注釋,也不會為這些命令調用自定義 CommandTargetResolver。 當命令創建聚合實例時,當命令成功執行時,該命令的回調將接收聚合標識符。

public class MyAggregate {

    @AggregateIdentifier
    private String id;

    @AggregateMember
    private MyEntity entity;

    @CommandHandler
    public MyAggregate(CreateMyAggregateCommand command) {
        apply(new MyAggregateCreatedEvent(...);
    }

    // no-arg constructor for Axon
    MyAggregate() {
    }

    @CommandHandler
    public void doSomething(DoSomethingCommand command) {
        // do something...
    }
}

public class MyEntity {

    @CommandHandler
    public void handleSomeCommand(DoSomethingInEntityCommand command) {
        // do something
    }
}

@CommandHandler 注釋不限於聚合根。將所有命令處理程序放置在根中有時會導致聚合根的大量方法,而其中許多方法簡單地將調用轉發到底層實體之一。如果是這種情況,則可以將 @CommandHandler 注釋放置在下面的實體之一“方法”上。對於Axon 找到這些注釋的方法,必須用 @AggregateMember 標記在聚合根中聲明實體的字段。請注意,只有聲明類型的注釋字段被檢查為命令處理程序。如果在傳入命令到達該實體時,字段值為 NULL,則會引發異常。

請注意,每個命令在聚合中必須有一個處理程序。這意味着您不能用使用 @CommandHandler 注釋處理相同命令類型的多個實體(無論是根節點還是不是根節點)。如果需要有條件地將命令路由到實體,則這些實體的父實體應該處理該命令,並根據適用的條件轉發命令。字段的運行時類型不必完全是聲明的類型。但是,對於 @CommandHandler 方法,只檢查@AggregateMenger 注釋字段的聲明類型。

也可以使用 @AggregateMember 注釋包含實體的 Collections 和 Maps。在后一種情況下,映射的值應包含實體,而鍵包含的值將用作其引用。

由於需要將命令路由到正確的實例,因此必須正確標識這些實例。他們的“id”字段必須用 @EntityId 注釋。將用於查找消息應該路由到的實體的命令上的屬性,默認為已注釋的字段的名稱。例如,在注釋字段“myEntityId”時,命令必須定義一個名稱相同的屬性。這意味着必須存在 getMyEntityId 或 myEntityId() 方法。如果字段的名稱和路由屬性不同,您可以使用 @EntityId(routingKey=“customRoutingProperty”) 顯式地提供一個值。

如果在帶注釋的 Collection 或 Map 中找不到任何 Entity,則 Axon 會引發 IllegalStateException;否則,它將拋出 IllegalStateException。 顯然,聚合不能在該時間點處理該命令。Collection 或 Map 的字段聲明應包含適當的泛型,以允許 Axon 標識 Collection 或 Map 中包含的實體的類型。 如果無法在聲明中添加泛型(例如,因為您使用的是已經定義了泛型類型的自定義實現),則必須在 @AggregateMember 批注中指定 entityType 屬性中使用的實體類型。

External Command Handlers

在某些情況下,不可能或希望將命令直接路由到 Aggregate 實例。在這種情況下,可以注冊命令處理程序對象。
Command Handler 對象是一個簡單的(常規)對象,具有 @CommandHandler 注釋方法。與聚合不同,命令處理程序對象只有一個實例,該對象處理在其方法中聲明的所有類型的命令。

public class MyAnnotatedHandler {

    @CommandHandler
    public void handleSomeCommand(SomeCommand command, @MetaDataValue("userId") String userId) {
        // whatever logic here
    }

    @CommandHandler(commandName = "myCustomCommand")
    public void handleCustomCommand(SomeCommand command) {
       // handling logic here
    }

}

// 要將注釋的處理程序注冊到命令總線,請執行以下操作
Configurer configurer = ...
configurer.registerCommandHandler(c -> new MyAnnotatedHandler());

Event Handling

Event listeners 是作用於傳入事件的組件。它們通常基於由命令模型作出的決定來執行邏輯。通常,這包括更新視圖模型或將更新轉發到其他組件,例如第三方集成。在某些情況下,事件處理程序將根據收到的事件(模式)拋出事件,或者甚至發送命令來觸發進一步的更改。

Defining Event Handlers

在 Axon 中,對象可以通過用 @eventHandler 注釋它們來聲明多個事件處理程序方法。該方法的聲明參數定義了它將接收的事件.

Axon 為以下參數類型提供開箱即用的支持:

  1. 第一個參數始終是事件消息的有效負載。如果事件處理程序不需要訪問消息的有效負載,則可以在 @EventHandler 注釋中指定預期的有效負載類型。當指定第一個參數時,將使用下面指定的規則解析第一個參數。如果希望將有效負載作為參數傳遞,請不要在注釋上配置有效負載類型。
  2. 用 @MetadataValue 注釋的參數將以注釋所示的鍵解析為元數據值。如果需要為 false(默認),則在元數據值不存在時傳遞 NULL。如果需要,則解析器將不匹配,並且在元數據值不存在時阻止該方法被調用。
  3. MetaData 類型的參數將注入 EventMessage 的整個 MetaData。
  4. 用 @Timestamp 注釋並且類型為 java.time.Instant(或 java.time.temporal.Temporal)的參數將解析為EventMessage 的時間戳。 這是生成事件的時間。
  5. 用 @SequenceNumber 注釋並且類型為 java.lang.Long 或 long 的參數將解析為 DomainEventMessage 的sequenceNumber。這提供了事件生成的順序(在其起源的聚合范圍內)。
  6. 可分配給 Message 的參數將注入整個 EventMessage(如果消息可分配給該參數)。如果第一個參數的類型為message,則它有效地匹配任何類型的事件,即使通用參數會另外建議也是如此。由於類型擦除,Axon 無法檢測到期望的參數。在這種情況下,最好聲明有效負載類型的參數,然后聲明消息類型的參數。
  7. 使用 Spring 並激活 Axon 配置時(通過包括 Axon Spring Boot Starter 模塊,或者通過在 @Configuration文件中指定 @EnableAxon),如果在控制台中只有一個可注入的候選對象,則任何其他參數都將解析為自動裝配的 bean。應用程序上下文。這使您可以將資源直接注入到 @EventHandler 帶注釋的方法中。

您可以通過實現 ParamResolerFactory 接口並創建名為 /META-INF/Service/org.axonframework.common.annotation.ParameterResolverFactory 的文件來配置其他參數解析器,其中包含實現類的完全限定名稱。

在所有情況下,每個監聽器實例最多調用一個事件處理程序方法。Axon 將使用以下規則搜索要調用的最特定方法:

  1. 在類層次結構的實際實例級別上(由this.getClass())返回),將評估所有帶注釋的方法。
  2. 如果找到一個或多個方法可以將所有參數解析為一個值,則選擇並調用具有最特定類型的方法。
  3. 如果在該類層次結構的此級別上沒有找到任何方法,則計算父類的方法是否相同。
  4. 當到達層次結構的頂層,而沒有找到合適的事件處理程序時,該事件將被忽略。
public class TopListener {

    @EventHandler
    public void handle(EventA event) {
    }

    @EventHandler
    public void handle(EventC event) {
    }
}

public class SubListener extends TopListener {

    @EventHandler
    public void handle(EventB event) {
    }

}

在上面的示例中,SubListener 將接收 EventB 和 EventC(擴展EventB) 的所有實例。換句話說,TopListener 根本不會接收 EventC 的任何調用。由於 Eventa 不能分配給 EventB(它是它的超類),這些類將由 TopListener 處理。

Registering Event Handlers

事件處理組件使用 EventHandlingConfiguration 類定義,該類被注冊為帶有全局 Axon 配置程序的模塊。通常,應用程序將定義單個 EventHandlingConfiguration,但更大的模塊化應用程序可以選擇每個模塊定義一個。

若要使用 @EventHandler 方法注冊對象,請在 EventHandlingConfiguration 上使用 RegistrerEventHandler方法:

// 定義EventHandlingConfiguration
EventHandlingConfiguration ehConfiguration = new EventHandlingConfiguration()
    .registerEventHandler(conf -> new MyEventHandlerClass());

// 該模塊需要在 Axon 配置中注冊。
Configurer axonConfigurer = DefaultConfigurer.defaultConfiguration().registerModule(ehConfiguration);


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM