Hej,
I am playing around a bit with Axon and hit a bit of a roadbump. I started by making an easy application with 1 aggregateroot and a few command and events, and everything went fine. Now I am trying to persist the events in mongodb and to have the aggregates be eventsourced. But i keep running into this exception:
Default configuration requires the use of event sourcing. Either configure an Event Store to use, or configure a specific repository implementation for class be.tribersoft.command.aggregate.TodoList
My Configuration looks like this:
@Configuration
public class AxonJavaConfig {
@Bean
EventBus eventBus() {
return new SimpleEventBus();
}
@Bean
public MongoClient mongo() throws UnknownHostException {
return new MongoClient("127.0.0.1", 27017);
}
@Bean
public MongoTemplate mongoSpringTemplate() throws UnknownHostException {
return new MongoTemplate(mongo(), "tribertodo");
}
@Bean
public org.axonframework.mongo.eventsourcing.eventstore.MongoTemplate mongoTemplate() throws UnknownHostException {
return new DefaultMongoTemplate(mongo(), "tribertodo", "domainevents", "snapshotevents");
}
@Bean
public EventStore eventStore() throws UnknownHostException {
return new EmbeddedEventStore(eventStorageEngine());
}
@Bean
public MongoEventStorageEngine eventStorageEngine() throws UnknownHostException {
return new MongoEventStorageEngine(mongoTemplate());
}
@Bean
public Repository<TodoList> todoListCommandRepository() throws UnknownHostException {
return new EventSourcingRepository<TodoList>(TodoList.class, eventStore());
}
@Bean
public TodoListCommandHandler todoListCommandHandler() throws UnknownHostException {
return new TodoListCommandHandler(todoListCommandRepository());
}
}
Seems i got it to work if i remove the @Aggregate annotation.
Though now i am facing this error:
org.axonframework.eventsourcing.IncompatibleAggregateException: Aggregate identifier must be non-null after applying an event. Make sure the aggregate identifier is initialized at the latest when handling the creation event.
It happens when i restart my application and try to issue a command on an aggregate root that has been created in the previous run of th eapplication.
My command handler looks like this:
open class TodoListCommandHandler(private val repository: Repository<TodoList>) {
@CommandHandler
open fun on(command: CreateTodoListCommand) {
repository.newInstance { TodoList(command.uuid, command.name) }
}
@CommandHandler
open fun on(command: UpdateTodoListNameCommand) {
val aggregate = repository.load(command.uuid.toString())
aggregate.execute { it.updateName(command.uuid, command.name) }
}
}
I tried using that annotation, but that didn’t change much. Maybe i did it wrong though. I am also basing myself on the axon-bank example and that one doesn’t use that annotation.
I do see this warning: ‘Multiple beans of type EventBus found in application context: [eventBus, eventStore]. Chose eventBus’ when starting up. But when i remove the eventBus from my config, i get the exception that no eventBus is defined :s. Can this have to do with my problem?
I did some more investigation. And i seem to need both the eventBus and the eventStore if i have another service that has @EventHandler.
For example i have a service to build a query model:
@Component
class TodoListService @Inject constructor(private val todoListRepository: TodoListRepository) {
val logger: Logger = LoggerFactory.getLogger(TodoListService::class.java)
@EventHandler
fun on(event: TodoListCreatedEvent) {
logger.info("Creatint todolist")
todoListRepository.save(TodoList(event.uuid.toString(), event.name))
}
}
But it fails when i don't have a EventBus like this:
there is a number of issues with the configuration. In Axon 3, the Event Store is an Event Bus (with a few extra features). So if you want to use event sourcing, use an Event Store only.
I recommend using @EnableAxon on your configuration file. It will configure a lot of things for you.
If you annotated your aggregate root with @Aggregate, Axon will then configure all components necessary to operate the aggregate. So you don’t have to define any repository or command handler component anymore. Just put the @CommandHandler annotation on methods in your aggregate class.
You only need to define an Event Storage Engine. The @EnableAxon annotation will then configure an Event Store for you. So in your case, that would be a MongoEventStorageEngine.
Although the @EventHandler and @EventSourcingHandler do exacty the same thing, it is recommended to use the @EventSourcingHandler annotation inside aggregates. It makes it slightly more explicit what the method is used for.
In Axon, it is important that the very first event applied by an aggregate sets the identifier field to a non-null value. Are you sure the UUID set form the Event is non-null? Also make sure there are no other events before this creation event.
I was sure there were some problems :p. Not really sure what I am doing at this stage. Though also the documentationf or Axon 3 and Spring is a bit lacking at the moment, though I understand you are working on it.
I have the @EnableAxon annotation on my spring boot main class, so that part works. I simplified the configuration to this:
@Configuration
public class AxonJavaConfig {
@Bean
public MongoClient mongo() throws UnknownHostException {
return new MongoClient("127.0.0.1", 27017);
}
@Bean
public MongoTemplate mongoSpringTemplate() throws UnknownHostException {
return new MongoTemplate(mongo(), "tribertodo");
}
@Bean
public org.axonframework.mongo.eventsourcing.eventstore.MongoTemplate mongoTemplate() throws UnknownHostException {
return new DefaultMongoTemplate(mongo(), "tribertodo", "domainevents", "snapshotevents");
}
@Bean
public MongoEventStorageEngine eventStorageEngine() throws UnknownHostException {
return new MongoEventStorageEngine(new JacksonSerializer(), null, mongoTemplate(), new DocumentPerEventStorageStrategy());
}
}
I can’t see anything wrong in your configuration, but I’m not too familiar with the effects of Kotlin on the bytecode. The fact that Koltin functions are final by default shouldn’t matter, but there could always be something else in the way.
Could you set a breakpoint in the ModelInspector class on line 197? This is the getHandler(…) method, in which the handler for a specific message is located. For your applied event, this should return a handler method.
Can you let me know if the eventHandlers field contains the entry that you would expect?
Hej,
I tried changing my aggregate to a java class but that doesn’t help.
I put a breakpoint there and i see that i have 2 handlers, one for the created and one for the updated event. But neither of them can handle the message.
The message is of the type GenericTrackedDomainEventMessage and the aggregateIdentifier is indeed the one i would expect . I can see that in this method:
from the AnnotatedMessageHandlingMember class that the payload is not assignable. The events are implemented using kotlin data classes (stole that idea from your youtube intro to axon), so something might be wrong there?
in my video, I don’t use data classes, but not for any specific reason. I would expect Kotlin classes to be compatible with the java ones. What is the value of the payloadType field and the message.getPayloadType() method? I would expect they are the same. Them not being “assignable” to eachother might mean that there is some classloading “magic” going on. What kind of environment do you run this in?
Hej,
I jus ttested with changing the event class to a java class and it is the same behaviour.
I also checked that when i created by using the command the isAssignable works, but when it gets loaded from the repository it doesn’t.
The payloadType and the getPayloadType do return the same class. When i navigate in intellij it even opens the same java/kotlin file :s.
What do you mean by environment, i am just using a spring boot class and java 8.
I have checked out the code, and when running the test cases, I only get NoHandlerForCommandException or tests that fail based on different values inside the events. It appears to me these exceptions make sense (the difference is in the timestamps, or the handlers for the command simply don’t exist on the TodoList class)
I didn’t get any of the exceptions indicating an uninitialized Aggregate identifier.
Oh right, i should have mentioned that the test don’t work anymore. I removed a lot of code to get down to the simplest case, so there is less noise. I should have disabled/deleted some tests.
I get the problem by running the code and follow these steps:
1 Create a todolist by doing a post on localhost:8080/todo-list with a json body of {“name”: “name”}. This works and creates a TodoListCreatedEvent
2 Update a todolist by doing a put on localhost:8080/todo-list/ with a json body of {“name”: “new name”}, where id is the uuid returned from step 1. I would expect that this would load up the aggregate by using the TodoListCreatedEvent from step 1 and then applying the UpdateTodoListNameCommand that would lead to a TodoListUpdatedEvent. The problem seems to be in the loading of the aggregate based on the TodoListCreatedEvent.
Thanks for looking, and sorry about the failing tests. I haven’t looked at doing tests with a mongodb, as i hope the test framework would just use some in memory stuff so i can keep the tests clean.
But the fact that the unit tests work (even though they fail) is an indication that the setup works.
I noticed you’re using spring-boot-devtools. I have heard issues of that in combination with Axon as it reloads classes. Axon does inspection at startup and is not able to deal with new classes being (re)loaded at runtime. That explains why you get two classes with the same name, but that are not assignable to eachother.
Disabling spring boot devtools should fix the problem.
In future releases, we will look at what we can do to support this.
Alright, I’ll have a look tonight if disabling the devtools works, pretty obscure thing to find. I thought i already stripped my pom files as much as possible :D.
Would be nice if it is supported, devtools speed up development time pretty much