由於須要用到,可是在網上對應的資料實在是不多,只有迎着頭皮看官網文檔並配合翻譯器。若有誤導多多包涵。java
Axon
什麼是 Axon
Axon Framework 經過支持開發人員應用命令查詢責任隔離(CQRS)架構模式來幫助構建可擴展和可維護的應用程序。它經過提供最重要的構建塊(例如聚合,存儲庫和事件總線(事件的分發機制))的實現來實現。此外 Axon 提供註釋支持,它使您能夠構建聚合和事件偵聽器,而無需將代碼與 Axon 特定的邏輯綁定在一塊兒。這使您能夠專一於業務邏輯而不是管道,並可使您的代碼更易於獨立測試。數據庫
並不是每一個應用程序都會從 Axon 中受益。不指望擴展的簡單 CRUD 應用程序可能不會從 CQRS 或 Axon 中受益。然而,有各類各樣的應用程序確實受益於 Axon。
可能受益於 CQRS 和 Axon 的應用:編程
- (系統功能須要頻繁迭代新功能)應用程序極可能在很長一段時間內使用新功能進行擴展。例如,在線商店可能從訂單模塊進度的系統開始。在稍後階段,這能夠經過庫存信息進行擴展,以確保庫存在出售時獲得更新。甚至在之後,會計能夠要求記錄銷售的財務統計,等等。雖然很難預測軟件項目在將來將如何發展,但大多數這類應用程序都是這樣的。
- (頻繁讀寫的應用)應用程序具備高的讀寫比。這意味着數據只寫幾回,並屢次讀。因爲查詢的數據源與用於命令驗證的數據源不一樣,所以能夠優化這些數據源以實現快速查詢。重複數據再也不是問題,由於數據更改時會發布事件。
- 應用程序以多種格式顯示數據。如今,許多應用程序在網頁上顯示信息時不會中止。例如,一些應用程序每個月發送電子郵件,通知用戶可能發生的與其相關的更改。搜索引擎是另外一個例子。它們使用的數據與應用程序使用的數據相同,但以一種爲快速搜索而優化的方式。報表工具將信息聚合到顯示數據隨時間演變的報表中。一樣,這也是同一數據的不一樣格式。使用 Axon,能夠在實時或計劃的基礎上獨立地更新每一個數據源。
- 當應用程序明顯地將組件與不一樣的對象分開時,它也能夠從 Axon 中受益。這種應用程序的一個例子是在線商店。員工將在網站上更新產品信息和可用性,而客戶則會發出訂單並查詢其訂單狀態。使用 Axon,這些組件能夠部署在不一樣的機器上,並使用不一樣的策略進行縮放。它們使用事件保持最新狀態,不管部署在哪臺機器上,Axon 都會將這些事件分派給全部訂閱的組件。
- 與其餘應用程序集成多是一項繁瑣的工做。使用命令和事件嚴格定義應用程序的 API 可使其更容易與外部應用程序集成。任何應用程序均可以發送命令或偵聽應用程序生成的事件。
架構概述
CQRS 自己是很是簡單的模式。它只規定處理命令的應用程序的組件應該與處理查詢的組件分離。雖然這種分離自己很是簡單,可是當與其它模式結合時,它提供了許多很是強大的特徵。安全
axon 提供構建塊,使得可以更容易地實現能夠與 CQRS 組合使用的不一樣模式。下圖顯示了基於CQRS的事件驅動架構的擴展布局的示例。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ae30iIc6-1570778817749)
左側顯示的 UI 組件以兩種方式與應用程序的其他部分交互:它嚮應用程序發送命令 Command(在頂部顯示),並查詢應用程序以獲取信息(在底部顯示)。架構
命令一般由簡單和直接的對象表示,對象包含命令處理程序執行該命令所需的全部數據。命令以其名稱表示其意圖。在 Java 術語中,這意味着使用類名稱來找出須要完成的操做,而且命令的字段提供執行該操做所需的信息。命令總線接收命令並將它們路由到命令處理程序。每一個命令處理程序響應特定類型的命令並基於命令的內容執行邏輯。可是,在某些狀況下,您還但願執行邏輯,而無論實際的命令類型,例如驗證、日誌記錄或受權。命令處理程序從存儲庫中檢索域對象(聚合),並對其執行方法以更改其狀態。這些聚合一般包含實際的業務邏輯,所以負責保護它們本身的不變量。app
聚合的狀態變化致使生成域事件。域事件和聚合都造成域模型。存儲庫負責提供對聚合的訪問。一般,這些存儲庫被優化用於僅經過其惟一標識符來查找聚合。一些存儲庫將存儲聚合自己的狀態(例如,使用對象關係映射),而其餘存儲庫存儲聚合已在事件存儲中經歷的狀態更改。存儲庫還負責持久存儲在其備份存儲中對聚合進行的更改。框架
Axon 爲持久聚合的直接方式(例如使用對象關係映射)和事件來源提供支持。事件總線將事件分派給全部感興趣的事件偵聽器。這能夠同步地或異步地完成。異步事件調度容許命令執行返回並將控制移交給用戶,而在後臺調度和處理事件。沒必要等待事件處理才能完成應用程序的響應。另外一方面,同步事件處理更簡單,是明智的默認處理。默認狀況下,同步處理將處理一樣處理該命令的同一事務中的事件偵聽器。事件偵聽器接收事件並處理它們。一些處理程序將更新用於查詢的數據源,而另外一些處理程序將消息發送到外部系統。正如您可能注意到的,命令處理程序徹底不知道它們所作的更改感興趣的組件。這意味着將應用擴展到新的功能是很是非侵入性的。您只需添加另外一個事件偵聽器。這些事件將應用程序中的全部組件鬆散地耦合在一塊兒。在某些狀況下,事件處理須要嚮應用程序發送新命令。異步
這是當接收到訂單時的示例。這可能意味着客戶的帳戶應借記購買的金額,而且必須告知裝運準備裝運所購買的貨物。在許多應用中,邏輯會變得更加複雜:若是客戶沒有及時付款怎麼辦?您將當即發貨,仍是先等待付款?Saga 是負責管理這些複雜業務交易的 CQRS 概念。用戶接口和數據源之間的薄數據層提供了與所使用的實際查詢實現的明肯定義的接口。此數據層一般將只讀DTO 返回到包含查詢結果的對象。這些 DTO 的內容一般由用戶接口的須要來驅動。在大多數狀況下,它們直接映射到 UI 中的特定視圖(也稱爲表視圖)。Axon 不能爲該部分的應用提供任何構建塊。主要緣由是這很是簡單,與分層體系結構不一樣。分佈式
模塊結構
AxonFramework 由多個模塊組成,這些模塊針對 CQRS 的特定問題區域。根據項目的確切需求,您須要包含一個或多個這些模塊。模塊化
- core: 模塊包含 Axon 的核心組件。若是使用單節點設置,則此模塊可能會提供所需的全部組件。全部其餘 Axon 模塊都依賴於這個模塊,所以它必須始終在類路徑上可用。
- test: 測試模塊包含測試工具,能夠用來測試基於 Axon 的組件,例如命令處理程序、Aggregates 和 Sagas。一般在運行時不須要這個模塊,只須要在測試期間添加到類路徑中。
- Distributed: 分佈式 CommandBus 模塊包含可用於在多個節點上分發命令的實現。它附帶了用於鏈接這些節點的JGroups 和 SpringCloud 鏈接器。
- AMQP: AMQP 模塊提供的組件容許您使用基於 AMQP 的消息代理做爲分發機制來構建 EventBus。這容許保證交付,即便事件處理程序節點暫時不可用。
- Spring: Spring 模塊容許在 SpringApplication 上下文中配置 Axon 組件.它還提供了許多特定於 Spring 框架的構建塊實現,例如在 Spring 消息通道上發佈和檢索 Axon 事件的適配器。
- 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 提供的構建塊使用。若是您確實須要訪問它,不管出於什麼緣由,有幾種方法能夠得到它。
- Handler 經過 Handle 方法中的參數接收工做單元。若是使用註釋支持,則能夠將 UnitOfWork 類型的參數添加到帶註釋的方法中。
- 在其餘位置,能夠經過調用 CurrentUnitOfWork.get() 檢索綁定到當前線程的工做單元。注意,若是沒有綁定到當前線程的工做單元,此方法將引起異常。使用 CurrentUnitOfWork.isStarted() 查找是否可用。要求訪問當前工做單元的緣由之一是,附加在消息處理過程當中須要屢次重複使用的資源,或者在工做單元完成時是否須要清理建立的資源。在這種狀況下,unitOfWork.getOrComputeResource() 和生命週期回調方法(如 onRollback()、postCommit() 和onCleanup() 容許您註冊資源並聲明在此工做單元處理過程當中要採起的操做。
請注意,工做單元只是變動的緩衝,而不是事務的替代。雖然全部分階段的更改都是在提交工做單元時才提交的,但它的提交併非原子的。這意味着,當提交失敗時,一些更改可能會持久化,而其餘更改則不會。最佳實踐規定,命令不該包含多個操做。若是你堅持這種作法,一個工做單位將包含一個單一的行動,使它安全地使用原樣。若是工做單元中有更多的操做,那麼能夠考慮將事務附加到 Work 的提交。 使用 unitOfWork.onCommit(…) 註冊在提交工做單元時須要執行的操做。
處理程序可能會因爲處理消息而引起異常。默認狀況下,未經檢查的異常將致使 UnitOfWork 回滾全部更改。Axon提供了一些開箱即用的回滾策略:
- RollbackConfigurationType.NEVER 將始終提交工做單元
- RollbackConfigurationType.ANY_THROWABLE 發生異常時將始終回滾
- RollbackConfigurationType.UNCHECKED_EXCEPTIONS 將回滾錯誤和運行時異常
- 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 偵聽器。
- Active phase: 工做單位已啓動。工做單位一般在此階段(經過 CurrentUnitOfWork.set(UNITOFWork))註冊。隨後,該消息一般由這個階段中的消息處理器處理。
- Commit phase: 在完成消息處理以後,但在提交工做單元以前,將調用 onPrepareCommit 偵聽器。若是工做單元綁定到事務,則調用 onCommit 偵聽器來提交任何支持事務。當提交成功時,將調用 postCommit 偵聽器。若是提交或以前的任何步驟失敗,則調用 onRollback 偵聽器。若是可用,消息處理程序結果包含在工做單元的 ExecutionResult中。
- 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 中,聚合由聚合標識符標識。這多是任何對象,可是對於標識符的良好實現有一些準則。標識符必須:
- 實現 eques 和 hashCode,以確保與其餘實例進行良好的平等比較
- 實現一個提供一致結果的 toString 方法
- 實現 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 註釋。此批註可用於多種字段類型:
- 在字段中直接引用的實體類型
- 包含 Iterable(包含全部集合,例如 Set,List 等)的內部字段
- 在包含 java.util.Map 的字段的值中
Handling commands in an Aggregate
建議直接在包含處理該命令狀態的 Aggregate 中定義命令處理程序,由於命令處理程序不太可能須要該 Aggregate 的狀態來完成其工做。
若要在聚合中定義命令處理程序,只需使用 @CommandHandler 註釋命令處理方法。@CommandHandler 註釋方法的規則與任何處理程序方法相同。然而,命令不只是由它們的有效負載路由的。命令消息帶有一個名稱,默認爲 Command 對象的徹底限定類名。
默認狀況下,@CommandHandler 註釋方法容許如下參數類型:
- 第一參數是命令消息的有效載荷。若是 @CommandHandler 註釋明肯定義了處理程序能夠處理的命令的名稱,則它也能夠是類型消息或 CommandMessage。缺省狀況下,命令名稱是命令的有效負載的徹底限定的類名稱。
- 用 @MetadataValue 註釋的參數將以註釋所示的鍵解析爲元數據值。若是須要爲 false(默認),則在元數據值不存在時傳遞 NULL。若是須要,則解析器將不匹配,而且在元數據值不存在時阻止該方法被調用。
- MetaData 類型的參數將注入 CommandMessage 的整個 MetaData。
- 類型爲 UnitOfWork 的參數獲取當前注入的工做單位。這使命令處理程序能夠註冊要在工做單元的特定階段執行的操做,或者訪問對其註冊的資源。
- 類型爲 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 爲如下參數類型提供開箱即用的支持:
- 第一個參數始終是事件消息的有效負載。若是事件處理程序不須要訪問消息的有效負載,則能夠在 @EventHandler 註釋中指定預期的有效負載類型。當指定第一個參數時,將使用下面指定的規則解析第一個參數。若是但願將有效負載做爲參數傳遞,請不要在註釋上配置有效負載類型。
- 用 @MetadataValue 註釋的參數將以註釋所示的鍵解析爲元數據值。若是須要爲 false(默認),則在元數據值不存在時傳遞 NULL。若是須要,則解析器將不匹配,而且在元數據值不存在時阻止該方法被調用。
- MetaData 類型的參數將注入 EventMessage 的整個 MetaData。
- 用 @Timestamp 註釋而且類型爲 java.time.Instant(或 java.time.temporal.Temporal)的參數將解析爲EventMessage 的時間戳。 這是生成事件的時間。
- 用 @SequenceNumber 註釋而且類型爲 java.lang.Long 或 long 的參數將解析爲 DomainEventMessage 的sequenceNumber。這提供了事件生成的順序(在其起源的聚合範圍內)。
- 可分配給 Message 的參數將注入整個 EventMessage(若是消息可分配給該參數)。若是第一個參數的類型爲message,則它有效地匹配任何類型的事件,即便通用參數會另外建議也是如此。因爲類型擦除,Axon 沒法檢測到指望的參數。在這種狀況下,最好聲明有效負載類型的參數,而後聲明消息類型的參數。
- 使用 Spring 並激活 Axon 配置時(經過包括 Axon Spring Boot Starter 模塊,或者經過在 @Configuration文件中指定 @EnableAxon),若是在控制檯中只有一個可注入的候選對象,則任何其餘參數都將解析爲自動裝配的 bean。應用程序上下文。這使您能夠將資源直接注入到 @EventHandler 帶註釋的方法中。
您能夠經過實現 ParamResolerFactory 接口並建立名爲 /META-INF/Service/org.axonframework.common.annotation.ParameterResolverFactory 的文件來配置其餘參數解析器,其中包含實現類的徹底限定名稱。
在全部狀況下,每一個監聽器實例最多調用一個事件處理程序方法。Axon 將使用如下規則搜索要調用的最特定方法:
- 在類層次結構的實際實例級別上(由this.getClass())返回),將評估全部帶註釋的方法。
- 若是找到一個或多個方法能夠將全部參數解析爲一個值,則選擇並調用具備最特定類型的方法。
- 若是在該類層次結構的此級別上沒有找到任何方法,則計算父類的方法是否相同。
- 當到達層次結構的頂層,而沒有找到合適的事件處理程序時,該事件將被忽略。
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);