Aggregate repositories

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());
    }
}

My aggregate looks like this (written in Kotlin)

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) }
    }
}

And the aggregateroot is:

In

   @EventHandler
    fun on(event: TodoListCreatedEvent) {
        uuid = event.uuid
    }

replace @EventHandler by @EventSourcingHandler and you can create another external class as an EventHandler with @ EventHandler annotation

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:

Hi,

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.

Cheers,

Allard

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());
    }

}
And the aggregateroot now looks like this:

Hi,

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?

Cheers,

Allard

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:


@Override
public boolean canHandle(Message<?> message) {
    return typeMatches(message) && payloadType.isAssignableFrom(message.getPayloadType()) && parametersMatch(message);
}

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?

Hi,

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?

Cheers,

Allard

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.

btw the github of the project is https://github.com/triberraar/triber-todo-es if that tells you anything more?
Thanks for the help so far, i am really stuck with this one.

Hi,

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.

Cheers,

Allard

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.

Cheers,

Allard

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

Removing spring devtools worked out nicely. Thanks, now i can continue playing around with it.