2017-10-18 138 views
1

我正在使用Axon + Spring Boot創建一個簡單的應用程序,只是爲了確保我在Axon框架中理解基本組件,然後纔將其用於實際項目。 有一個應該當我通過CommandGateway命令發送到被稱爲類TaskAggregate內的@CommandHandler註釋的方法,但在運行應用程序後,我得到異常:NoHandlerForCommandException with axon-spring-boot-starter

Exception in thread "main" org.axonframework.commandhandling.NoHandlerForCommandException: No handler was subscribed to command [com.xxx.axontest.task.CreateTaskCommand] 

按文檔,@CommandHandler註釋應該足以將命令hander訂閱到命令總線。我想我一定會錯過一些東西。你可以看看下面的代碼,並指出我正確的方向?

的pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 
    <modelVersion>4.0.0</modelVersion> 
    <groupId>com.xxx</groupId> 
    <artifactId>axon-test</artifactId> 
    <version>0.0.1-SNAPSHOT</version> 

    <properties> 
     <axon.version>3.0.6</axon.version> 
    </properties> 

    <parent> 
     <groupId>org.springframework.boot</groupId> 
     <artifactId>spring-boot-starter-parent</artifactId> 
     <version>1.5.7.RELEASE</version> 
    </parent> 

    <dependencies> 

     <dependency> 
      <groupId>org.springframework.boot</groupId> 
      <artifactId>spring-boot-starter-web</artifactId> 
     </dependency> 

     <dependency> 
       <groupId>org.axonframework</groupId> 
       <artifactId>axon-spring-boot-starter</artifactId> 
       <version>${axon.version}</version> 
     </dependency> 

    </dependencies> 

    <build> 
     <plugins> 
      <plugin> 
       <groupId>org.springframework.boot</groupId> 
       <artifactId>spring-boot-maven-plugin</artifactId> 
      </plugin> 
     </plugins> 
    </build> 

</project> 

App.java

package com.xxx.axontest; 

import org.axonframework.commandhandling.gateway.CommandGateway; 
import org.axonframework.eventsourcing.eventstore.EventStorageEngine; 
import org.axonframework.eventsourcing.eventstore.inmemory.InMemoryEventStorageEngine; 
import org.springframework.boot.SpringApplication; 
import org.springframework.boot.autoconfigure.SpringBootApplication; 
import org.springframework.context.ConfigurableApplicationContext; 
import org.springframework.context.annotation.Bean; 

import com.xxx.axontest.task.CreateTaskCommand; 

@SpringBootApplication 
public class App { 

    public static void main(String[] args) { 
     ConfigurableApplicationContext configurableApplicationContext = SpringApplication.run(App.class, args); 
     CommandGateway commandGateway = configurableApplicationContext.getBean(CommandGateway.class); 
     commandGateway.send(new CreateTaskCommand(123, "asd")); 
    } 

    @Bean 
    public EventStorageEngine eventStorageEngine() { 
     return new InMemoryEventStorageEngine(); 
    } 

    @Bean 
    public AnnotationCommandHandlerBeanPostProcessor 
annotationCommandHandlerBeanPostProcessor() { 
    return new AnnotationCommandHandlerBeanPostProcessor(); 
    } 
} 

CreateTaskCommand.java

package com.xxx.axontest.task; 

import org.axonframework.commandhandling.TargetAggregateIdentifier; 

public class CreateTaskCommand { 

    @TargetAggregateIdentifier 
    private int taskId; 
    private String name; 

    public CreateTaskCommand(int taskId, String name) { 
     this.taskId = taskId; 
     this.name = name; 
    } 

    public int getTaskId() { 
     return taskId; 
    } 

    public String getName() { 
     return name; 
    } 
} 

TaskCreatedEvent.java

package com.xxx.axontest.task; 

import org.axonframework.commandhandling.TargetAggregateIdentifier; 

public class TaskCreatedEvent { 

    @TargetAggregateIdentifier 
    private int taskId; 
    private String name; 

    public int getTaskId() { 
     return taskId; 
    } 

    public String getName() { 
     return name; 
    } 

} 

TaskAggregate.java

package com.xxx.axontest.task; 

import org.apache.log4j.LogManager; 
import org.apache.log4j.Logger; 
import org.axonframework.commandhandling.CommandHandler; 
import org.axonframework.commandhandling.model.AggregateIdentifier; 
import org.axonframework.commandhandling.model.AggregateLifecycle; 
import org.axonframework.eventsourcing.EventSourcingHandler; 
import org.axonframework.spring.stereotype.Aggregate; 

@AggregateRoot 
public class TaskAggregate { 

    private Logger logger = LogManager.getLogger(TaskCreatedEvent.class); 

    @AggregateIdentifier 
    private int taskId; 
    private String name; 

    @CommandHandler 
    public void handleCommand(CreateTaskCommand createTaskCommand) { 
     logger.info("Command received"); 
     AggregateLifecycle.apply(new TaskCreatedEvent()); 
    } 

    @EventSourcingHandler 
    public void onTaskCreatedEvent(TaskCreatedEvent taskCreatedEvent) { 
     logger.info("Event received"); 
    } 

    public int getTaskId() { 
     return taskId; 
    } 

    public void setTaskId(int taskId) { 
     this.taskId = taskId; 
    } 

    public String getName() { 
     return name; 
    } 

    public void setName(String name) { 
     this.name = name; 
    } 

} 

在此先感謝。

+0

一件重要的事情:事件和命令應該是不可變的(無設置器) –

+0

從doc:「CommandHandlers需要訂閱CommandBusin命令才能接收命令「 –

+0

@ConstantinGalbenu,我根據你的建議刪除了setter。 – Rodney

回答

2

我認爲你需要用@Aggregate而不是@AggregateRoot來標註你的聚合。作爲註釋的 @Aggregate既是@AggregateRoot註釋,也被Spring Boot Axon Starter模塊用來表示該類別的聚合工廠和Repository必須被創建。 此外,這將意味着您的聚合中的@CommandHandler註釋函數也會被找到並註冊到CommandBus,可能會解決您捕獲的異常。

否則,來自Allard的YouTube上的webinars在版本3中啓動Axon應用程序可能會給您一些洞察。

但是,總之,嘗試另外然而切換@AggregateRoot標註爲@Aggregate :-)

,你應該調整爲CreateTaskCommand@CommandHandler註釋功能是爲TaskAggregate構造。 最後,Axon要求你有一個沒有參數的構造函數給你,所以也在那裏添加一個public TaskAggregate() { }構造函數。

+0

好的。我試過了,現在我得到** Command'com.xxx.axontest.task.CreateTaskCommand'導致org.axonframework.commandhandling.model.AggregateNotFoundException(在事件存儲中未找到聚合)** – Rodney

+0

調整了評論指出你應該可以通過構造函數而不是常規函數來處理'CreateTaskCommand'。另外,也是一個無參數構造函數,正如Axon所要求的那樣。 – Steven

0

您還需要將您的處理程序註冊到命令總線。我發現this tutorial應該可以幫到你。從那裏快速突出:

@Configuration 
public class AppConfiguration { 

    @Bean 
    public SimpleCommandBus commandBus() { 
     SimpleCommandBus simpleCommandBus = new SimpleCommandBus(); 
     // This manually subscribes the command handler: 
     // DebitAccountHandler to the commandbus. 
     simpleCommandBus.subscribe(DebitAccount.class.getName(), new DebitAccountHandler()); 
     return simpleCommandBus; 
    } 
} 

P.S.一個重要的事情:事件和命令應該是不可變的(不是setter)

+0

你提到的教程是使用軸2.4.1。我正在使用3.0.6。在[axon的文檔](https://docs.axonframework.org/v/3.0/part3/spring-boot-autoconfig.html),Aggregate Configuration部分中,他們說:** Axon會自動註冊所有的@CommandHandler註釋方法如果不存在** – Rodney

+0

在本教程中,您提到他們也會說: **如果在Spring中使用Axon,請使用AnnotationCommandHandlerBeanPostProcessor,它允許我們擁有Spring bean,其中有註釋了方法的Spring bean @CommandHandler會變成命令處理程序。** – Rodney

+0

在我的項目中,命令處理程序在聚合中。在axon 3.0.6中沒有可以繼承的CommandHandler接口。 – Rodney

2

基於上面的代碼,幾句話:

  • 你不需要提供AnnotationCommandHandlerBeanPostProcessor。實際上,指定一個可能會干擾Axon/Spring Boot自動配置的正常運行。
  • 創建新的Aggregate實例的命令應放置在構造函數中。尚未調用該方法的實例。請注意,您將(也)必須指定一個無參數構造函數。
  • taskId應該由@EventSourcingHandler設置。吸氣劑和吸氣劑不屬於(事件源)聚合物
  • 在活動中,您不需要指定@TargetAggregateIdentifier。它們只是命令的手段。

根據您提供的代碼,我無法解釋這個異常,但是顯式定義的AnnotationCommandHandlerBeanPostProcessor有可能會出現。

Steven正確指出了@AggregateRoot註解。它應該是@Aggregate。以上評論仍然有效,但並不直接涉及例外[/編輯]

+0

有點困惑,因爲[documentation](https://docs.axonframework.org/v/3.0/part3/spring-boot-autoconfig.html)說:** Axon會自動註冊所有CommandHandler註釋的方法命令總線,並建立一個存儲庫,如果沒有。**沒有提到構造函數。但是,如您所建議的那樣,在構造函數中使用@CommandHandler註釋而不是方法,可以解決問題。非常感謝你。 – Rodney