天天看點

Axon架構使用指南(二):入門

       本節将解釋如何擷取Axon的二進制檔案以開始使用。目前有兩種方法:從我們的網站下載下傳二進制檔案或為您的建構系統配置存儲庫(Maven,Gradle等)。

下載下傳Axon

        您可以從我們的下載下傳頁面下載下傳Axon Framework。

       此頁面提供了許多下載下傳。通常,您會想要使用最新的穩定版本。但是,如果您渴望開始使用最新且最強大的功能,則可以考慮使用快照版本。下載下傳頁面包含許多可供下載下傳的程式集。其中一些僅提供Axon庫本身,而另一些則提供Axon依賴的庫。還有一個“完整的”zip檔案,其中包含了Axon及其依賴項,來源和文檔,所有檔案都在一次下載下傳中。

         如果你真的想保持處于開發的前沿,你可以克隆Git倉庫:git://github.com/AxonFramework/AxonFramework.git,或者通路https://github.com/AxonFramework/AxonFramework來浏覽線上的源代碼。

配置Maven

如果您使用maven作為建構工具,則需要為項目配置正确的依賴關系。 在您的依賴項部分添加以下代碼:

<dependency> 
    <groupId>org.axonframework</groupId> 
    <artifactId>axon-core</artifactId> 
    <version>${axon.version</version> 
</dependency>
           

Axon架構提供的大部分功能都是可選的,并且需要額外的依賴關系。 我們選擇不預設添加這些依賴關系,因為它們可能會使用您不需要的構件給您的項目添亂。

基礎設施要求

         Axon架構不會對基礎架構施加很多要求。 它已經針對Java 8進行了建構和測試,或多或少成為唯一的要求。由于Axon本身不建立任何連接配接或線程,是以在Application Server上運作是安全的。 Axon通過使用Executor來抽象所有異步行為,例如,您可以很容易地傳遞容器管理的Thread Pool作為參數。 如果您沒有使用完整的應用程式伺服器(例如Tomcat,Jetty或獨立應用程式),則可以使用Executors類或SpringFramework來建立和配置線程池。

當你遇到問題時

         在實施您的應用程式時,您可能遇到問題,想知道某些情況的發生方式,或者有一些問題需要答案。 Axon使用者郵件清單可以幫助您。 隻需發送電子郵件至[email protected]。其他使用者以及Axon Framework的貢獻者可以幫助解決您的問題。如果發現錯誤,可以通過https://github.com/AxonFramework/AxonFramework/issues報告。報告問題時,請確定清楚地描述問題。解釋你做了什麼,結果是什麼以及你期望發生什麼。如果可能,請提供一個非常簡單的單元測試(JUnit)來顯示問題。 這使得修複更簡單。

為Axon架構做出貢獻

       Axon架構的開發從未完成。 我們希望在我們的架構中包含更多的功能,以便繼續開發可擴充的可擴充應用程式。這意味着我們不斷尋求幫助開發我們的架構。您可以通過多種方式為Axon架構做出貢獻:

  • 您可以在我們的問題頁面上報告任何錯誤,功能要求或改進建議:https://github.com/AxonFramework/AxonFramework/issues。所有想法都歡迎。報告錯誤時請盡可能準确。這将幫助我們重制問題,進而更快地解決問題。
  • 如果您為您自己的應用程式建立了一個元件,而您認為這些元件可能會包含在架構中,請向我們發送包含源代碼的修補程式或zip。 我們将對其進行評估并嘗試将其納入架構。 請確定使用javadoc正确記錄代碼。 這有助于我們了解正在發生的事情。
  • 如果您知道您認為可以幫助我們的任何其他方式,請不要猶豫,向Axon Framework郵件清單發送消息。

架構概述

        CQRS本身就是一個非常簡單的模式。 它隻規定處理指令的應用程式元件應與處理查詢的元件分離。雖然這種分離本身非常簡單,但是當與其他模式結合使用時,它提供了許多非常強大的功能。 Axon提供了構模組化塊,可以更輕松地實作可與CQRS結合使用的不同模式。下圖顯示了基于CQRS的事件驅動架構的擴充布局示例。 顯示在左側的UI元件以兩種方式與應用程式的其餘部分互動:它向應用程式發送指令(如上部分所示),并向應用程式查詢資訊(如下部分所示)。

Axon架構使用指南(二):入門

      指令通常由簡單而直接的對象表示,這些對象包含執行指令處理程式所需的所有資料。一個指令用名字表達它的意圖。對Java語言來說,這意味着使用類名來确定需要完成什麼,并且指令的各個字段提供了執行此操作所需的資訊。指令總線接收指令并将它們路由到指令處理程式。每個指令處理程式都會響應特定類型的指令,并根據指令的内容執行邏輯。在某些情況下,您也可以執行如驗證,日志記錄或授權邏輯。

        指令處理程式從存儲庫中檢索域對象(聚合)并執行它們的方法來更改它們的狀态。這些聚合通常包含實際的業務邏輯,是以負責維護自己的狀态。聚合的狀态變化導緻産生領域事件。領域事件和聚合形成領域模型。

        存儲庫負責提供對聚合的通路。通常,這些存儲庫僅通過聚合的唯一辨別符查找聚合。一些存儲庫将存儲聚合本身的狀态(例如使用對象關系映射),而另一些則在一個事件存儲庫中儲存聚合經曆的狀态改變過程。存儲庫還負責在其後備存儲中保留對聚合所做的更改。

      在某些情況下,事件處理需要将新指令發送給應用程式。一個例子是收到訂單時。這可能意味着客戶的賬戶應該以購買金額記帳,并且必須通知運送準備運送所購買的商品。在許多應用中,邏輯将變得比這更複雜:如果客戶沒有及時付款,該怎麼辦?您會立即寄出貨物,還是先等待付款? Saga是CQRS中的概念,用于管理這些複雜的業務事物。

       自從Axon 3.1架構提供了處理查詢的元件。 查詢總線接收查詢并将它們路由到查詢處理程式。查詢處理程式被注冊在查詢總線中,包括處理查詢的類型以及查詢響應的類型。

       查詢和結果類型通常都是簡單的隻讀DTO對象。 這些DTO的内容通常由使用者界面的需求驅動。在大多數情況下,它們直接映射到UI中的特定視圖。

        可以為同一類型的查詢和響應類型注冊多個查詢處理程式。當分派查詢時,客戶可以訓示他是想要一個結果還是來自所有可用的查詢處理程式;

子產品結構

      Axon架構由一些針對CQRS特定問題領域的子產品組成。根據項目的确切需要,您需要包含一個或多個這些子產品。從Axon 2.1開始,所有子產品都是OSGi相容的軟體包。

這意味着在清單檔案中已經包含必需的頭檔案并聲明它們導入和導出的包。目前,隻需要Slf4J軟體包(1.7.0 <版本<2.0.0)。 所有其他導入都标記為可選,盡管您很可能需要其他的導入。

 主要子產品

         Axon的主要子產品是經過全面測試的子產品,并且足夠健壯,可用于要求苛刻的生産環境。所有這些子產品的maven groupId是org.axonframework。

         正如其名稱所示,核心子產品包含了Axon的核心元件。如果您使用單節點安裝程式,則該子產品可能會提供您需要的所有元件。所有其他的Axon子產品都依賴于這個子產品,是以它必須始終在類路徑中可用。

         測試子產品包含可用于測試基于Axon的元件的測試裝置,例如您的指令處理程式,聚合和Sagas。您通常在運作時不需要此子產品,隻需在測試期間将其添加到類路徑中即可。

         分布式CommandBus子產品包含可用于在多個節點上分發指令的實作。它帶有用于連接配接這些節點的JGroups和Spring Cloud連接配接器。

        AMQP子產品提供的元件允許您使用基于AMQP的消息代理作為分發機制來建構EventBus。這允許有保證的傳輸,即使事件處理程式節點暫時不可用。

        Spring子產品允許在Spring應用程式上下文中配置Axon元件。 它還提供了許多特定于SpringFramework的建構塊實作,例如用于在Spring消息通道上釋出和檢索Axon事件的擴充卡。

       MongoDB是一個基于文檔的NoSQL資料庫。 Mongo子產品提供Event和Saga Store實作,将事件流和傳奇存儲在MongoDB資料庫中。

         幾個AxonFramework元件提供監視資訊。 度量子產品提供了基于Codehale的基本實作來收集監控資訊。

使用Axon API

        CQRS是一種架構模式,無法提供适合所有項目的單一解決方案。顯然,Axon架構并不試圖提供這種解決方案。取而代之的是,Axon提供了遵循最佳實踐的實作方法,以及根據您的具體要求調整每個實作的方法。

        幾乎所有的基礎設施建構塊都會提供鈎子點(例如攔截器,解析器等),允許您将特定于應用程式的行為添加到這些建構塊。在很多情況下,Axon将為那些适合大多數用例的鈎子點提供實作。如果需要,你可以簡單地實作你自己的。

       非基礎結構對象(如消息)通常是内容不可變的。這確定了這些對象可以安全地在多線程環境中使用,而沒有副作用。

       為確定最大限度的定制,所有的Axon元件都使用接口進行定義。提供了抽象和具體的實作,以幫助你以你的方式定制。始終可以使用該接口建構任何建構塊的完全自定義實作。

Spring 支援

       Axon Framework為Spring提供了廣泛的支援,但并不要求您使用Spring來使用Axon。所有元件都可以通過程式設計進行配置,并且不需要類路徑中的Spring。 但是,如果你使用Spring,通過使用Spring的注解支援,許多配置變得更加容易

消息傳遞的概念

       Axon的核心概念之一就是通訊。 元件之間的所有通信都使用消息對象完成。 這為這些元件提供了所需的位置透明度,以便在必要時擴充和配置設定這些元件。

       盡管所有這些消息都實作了消息接口,但消息的不同類型和消息的處理方式之間有明顯的差別。所有消息都包含有效負載,中繼資料和唯一辨別符。

       消息的有效載荷是消息的内容。中繼資料允許您描述消息發送的上下文。例如,您可以存儲跟蹤資訊,以便跟蹤消息的來源或原因。您還可以在中繼資料中存儲資訊來描述正在執行指令的安全上下文。

      請注意,所有消息都是不可變的。将資料存儲在消息中實際上意味着根據前一個消息建立新的消息,并添加額外的資訊。這保證了消息可以安全地在多線程和分布式環境中使用。

指令

      指令描述了改變應用程式狀态的意圖。它們被實作為(最好是隻讀的)POJO并使用CommandMessage的實作來進行封裝。

       指令總是隻有一個目的地。盡管發件人并不關心哪個元件處理指令或元件駐留的位置,但對知道它的結果可能很有趣。這就是為什麼通過指令總線發送的指令消息允許傳回結果的原因。

事件

      事件是描述應用程式中發生的事件的對象。事件的典型來源是聚合。在Aggregate内發生重要事件時,它将引發一個事件。 在Axon Framework中,事件可以是任何對象。 強烈建議您確定所有事件都是可序列化的。

      當事件分派時,Axon将它們包裝在一個EventMessage中。使用的消息的實際類型取決于事件的來源。當一個事件由一個Aggregate引發時,它被包裝在一個DomainEventMessage(它擴充了EventMessage)中。 所有其他事件都包裝在一個EventMessage中。 除了像唯一辨別符這樣的常見消息屬性外,EventMessage還包含一個時間戳。DomainEventMessage還包含引發該事件的聚合的類型和辨別符。 它還包含聚集事件流中事件的序列号,它允許重制事件的順序。

     注意,即使DomainEventMessage包含對AggregateIdentifier的引用,您也應始終在實際的Event中包含辨別符。雖然EventEventMessage使用DomainEventMessage中的辨別符來存儲事件,但對于其它情況不會提供一個可靠的值作為辨別符。

      原始的Event對象存儲為EventMessage的Payload。 在有效負載旁邊,您可以将資訊存儲在事件消息的中繼資料中。中繼資料的目的是存儲有關事件的附加資訊,該事件主要不是作為業務資訊。審計資訊就是一個典型的例子。它允許您檢視在哪些情況下引發了事件,例如觸發處理的使用者帳戶或處理該事件的計算機的名稱。

      一般而言,您不應将業務的決策基于事件消息的中繼資料中的資訊。如果是這樣的話,你可能得到的是事件本身的附加資訊而非業務内容資訊。中繼資料通常用于報告,審計和跟蹤。

      雖然這不是強制的,但最好将所有字段設為final,并通過在構造函數中初始化,來讓領域事件的内容不可變。 如果事件結構過于繁瑣,請考慮使用Builder模式。

      盡管領域事件在技術上表明了狀态的變化,但您也應該嘗試在事件中捕捉到狀态的意圖。一個好的做法是使用領域事件的抽象實作來捕獲某個狀态已經改變的事實,并使用該抽象類的具體子實作來訓示改變的意圖。例如,您可以擁有一個抽象的AddressChangedEvent,以及兩個實作ContactMovedEvent和AddressCorrectedEvent的捕獲狀态更改的意圖。 一些監聽器不關心意圖(例如資料庫更新事件監聽器)。這些将會聽取抽象類型。其他監聽器确實關心意圖,他們會監聽到具體的子類型(例如,向客戶發送位址更改确認電子郵件)。

Axon架構使用指南(二):入門

         在事件總線上分派事件時,您需要将其包裝在事件消息中。 GenericEventMessage是一個實作,它允許你将消息中的事件包裝起來。 您可以使用構造函數或靜态的asEventMessage()方法。 後者檢查給定參數是否已經實作了消息接口。如果是這樣,它要麼直接傳回(如果它實作了EventMessage),要麼使用給定消息的有效載荷和中繼資料傳回一個新的GenericEventMessage。 如果一個事件由一個聚合apply(或publish),Axon将會自動将該事件包裝在一個DomainEventMessage中,該消息包含聚集的辨別符,類型和序列号。

查詢

       查詢描述了對資訊或狀态的請求。一個查詢可以有多個處理程式。當分派查詢時,用戶端訓示他是想要一個結果還是來自所有可用的查詢處理程式

工作單元(Unit of Work)

       工作單元是Axon架構中的一個重要概念,但在大多數情況下,您不可能直接與其互動。消息的處理被視為一個單元。工作單元的目的是協調處理消息(指令,事件或查詢)期間執行的操作。元件可以注冊要在工作單元的每個階段執行的操作,例如onPrepareCommit或onCleanup。您不太可能需要直接通路工作單元。它主要由Axon提供的構模組化塊使用。如果您出于任何原因确定需要通路它,有幾種方法可以獲得它。處理程式通過handle方法中的參數接收工作單元。如果您使用注解,則可以将一個UnitOfWork類型的參數添加到注解的方法中。在其他位置,您可以通過調用CurrentUnitOfWork.get()來檢索綁定到目前線程的工作單元。請注意,如果沒有工作單元綁定到目前線程,則此方法将引發異常。使用CurrentUnitOfWork.isStarted()查找是否有可用的工作單元。

        要求通路目前工作單元的一個原因是附加需要在消息處理過程中多次重複使用的資源,或者在工作單元完成時需要清理建立的資源。在這種情況下,unitOfWork.getOrComputeResource()和onRollback(),afterCommit()和onCleanup()等生命周期回調方法允許您對所需資源進行注冊并聲明在處理本工作單元期間要采取的操作。

       請注意,工作單元僅僅是變化的緩沖區,而不是事務的替代。雖然所有分階段的變更隻是在工作單元送出時才送出,但其送出不是原子的。這意味着,如果送出失敗,一些更改可能會繼續保留,而其他更改則不會。最佳實踐規定Command不應包含多個操作。 如果你堅持這種做法,一個工作單元将包含一個單一的操作,使它可以安全地使用。如果您在工作單元中有更多的操作,那麼您可以考慮将事務附加到工作單元中進行送出,例如,使用unitOfWork.onCommit(..)來注冊工作單元送出時需要采取的事務操作。

       當處理消息時,您的處理程式可能會抛出異常。預設情況下,未經檢查的異常将導緻UnitOfWork復原所有更改。 使得預期的副作用被取消。

Axon提供了一些開箱即用的復原政策:

  • RollbackConfigurationType.NEVER将始終送出工作單元
  • RollbackConfigurationType.ANY_THROWABLE,将在發生異常時始終復原
  • RollbackConfigurationType.UNCHECKED_EXCEPTIONS将復原到錯誤和運作時異常
  • RollbackConfigurationType.RUNTIME_EXCEPTION将在運作時異常中復原(但不包括錯誤)

        使用Axon元件處理消息時,工作單元的生命周期将自動為您管理。 如果您選擇不使用這些元件,而是實作自己的處理,則需要以程式設計方式啟動并送出(或復原)工作單元。在大多數情況下,DefaultUnitOfWork将為您提供所需的功能。 它期望處理發生在單個線程内。要在Unit Of Work中執行任務,隻需在新的DefaultUnitOfWork上調用UnitOfWork.execute(Runnable)或UnitOfWork.executeWithResult(Callable)即可。 工作單元将在任務完成時啟動并送出,或者在任務失敗時復原。如果您需要更多控制,您也可以選擇手動啟動,送出或復原工作單元。

典型用法如下:

工作機關有幾個階段。每次進入另一階段時,都會通知UnitOfWork偵聽器:

UnitOfWork uow = DefaultUnitOfWork.startAndGet(message);
//接着,或者使用自動送出方式: uow.executeWithResult(() -> ... 這裡寫邏輯);
//或者手動送出或復原:
try {
//這裡寫業務邏輯 
   uow.commit();
} catch (Exception e) { 
   uow.rollback(e);
//也可以重新抛出異常...
}
           
  • 活動階段:這是工作單元開始的地方。工作單元通常在此階段通過CurrentUnitOfWork.set(UnitOfWork)注冊到目前線程中。随後通常由消息處理程式處理消息。
  • 送出階段:在完成消息處理之後但在送出工作單元之前,會調用onPrepareCommit偵聽器。 如果工作單元綁定到事務,則調用onCommit偵聽器來送出任何支援事務,當送出成功時,調用afterCommit監聽器。 如果送出或任何步驟失敗,則會調用onRollback偵聽器。 如果可用,消息處理程式結果包含在Unit Of Work的ExecutionResult中。
  • 清理階段:這是本工作單元擁有的任何資源(如鎖)将被釋放的階段。如果多個工作單元嵌套,則清理階段被推遲,直到外部工作單元準備清理。

      消息處理過程可以被認為是一個原子程式; 它應該完全處理,或者根本不處理。 Axon Framework使用Unit Of Work來跟蹤消息處理程式執行的操作。處理程式完成後,Axon将嘗試送出在Unit Of Work中注冊的操作。

      可以将事務綁定到工作單元。許多元件(如CommandBus和QueryBus實作以及所有異步處理事件處理器)都允許您配置事務管理器。此事務管理器将用于建立事務,并将它綁定到管理消息處理的工作單元。

      當應用程式元件需要得到消息處理的不同階段(如資料庫連接配接或EntityManager)中的資源時,可以将這些資源連接配接到工作單元。通過 unitOfWork.getResources()方法允許您通路附加到目前工作單元的資源。直接在工作單元上提供的這幾種幫助方法,會使得處理資源更加輕松。

       當嵌套的工作單元需要能夠通路資源時,建議可以使用unitOfWork.root()得到根工作單元并将資源注冊給它。

配置API

        Axon在業務邏輯和基礎架構配置方面保持嚴格的分離。 為了做到這一點,Axon将提供一些構模組化塊來處理基礎設施問題,例如消息處理器周圍的事務管理。 消息的實際有效負載的處理應該盡可能在與Axon無關的Java類中實作。

         為了簡化這些基礎設施元件的配置并定義它們與每個功能元件的關系,Axon提供了一個配置API。

         擷取預設配置非常簡單:           

Configuration config =DefaultConfigurer.defaultConfiguration().buildConfiguration();
           

        此配置提供建構塊,用于消息分發,這些消息被分發消息所在的線程内的消息處理器進行處理。顯然,這種配置不會很有用。您必須将您的指令模型對象和事件處理程式注冊到此配置才有用。

為此,請使用.defaultConfiguration()方法傳回的Configurer執行個體。

Configurer configurer =DefaultConfigurer.defaultConfiguration();
           

配置器提供了許多方法,允許您注冊這些元件。如何配置這些内容将在每個元件的相應章節中詳細介紹。

元件注冊的一般形式如下:

Configurerconfigurer = DefaultConfigurer.defaultConfiguration();
configurer.registerCommandHandler(c -> doCreateComponent());
           

     請注意registerCommandBus調用中的lambda表達式。此表達式的c參數是描述完整配置的配置對象。 如果您的元件需要其他元件正常運作,則可以使用此配置來擷取它們。例如,要注冊需要序列化程式的指令處理程式,請執行以下操作:

configurer.registerCommandHandler(c-> new MyCommandHandler(c.serializer());
           

     并非所有元件都有其明确的通路方法。要從配置中擷取元件,請使用:

    該元件必須使用configurer.registerComponent(componentType,builderFunction)向Configurer注冊。 建構器函數将接收Configuration對象作為輸入參數。

configurer.registerCommandHandler(c-> newMyCommandHandler(c.getComponent(MyOtherComponent.class));
           

使用Spring設定配置

      使用Spring時,不需要明确使用配置器。 相反,您可以簡單地将@EnableAxon放在您的某個[email protected]類上。Axon将使用Spring應用程式上下文來定位建構塊的特定實作,并為那些不在那裡的構件提供預設值。 是以,在Spring中,您不必使用配置器來注冊建構塊,而隻需将它們作為@Bean在應用程式上下文中使用即可。

繼續閱讀