Aggregate repositories

1,338 views
Skip to first unread message

Geert Olaerts

unread,
Oct 15, 2016, 10:24:06 AM10/15/16
to Axon Framework Users
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)
@Aggregate
@AggregateRoot
class TodoList {

val logger: Logger = LoggerFactory.getLogger(this.javaClass)

@AggregateIdentifier
var uuid: UUID? = null

constructor()

constructor(uuid: UUID, name: String) {
logger.info("Creating todolist: " + uuid)
AggregateLifecycle.apply(TodoListCreatedEvent(uuid, name))
}

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

And the command handler (also in Kotlin):
open class TodoListCommandHandler(private val repository: Repository<TodoList>) {

@CommandHandler
open fun on(command: CreateTodoListCommand) {
repository.newInstance { TodoList(command.uuid, command.name) }
}
}

So no idea what I am missing, any help appreciated.

Geert Olaerts

unread,
Oct 15, 2016, 11:56:37 AM10/15/16
to Axon Framework Users
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:
@AggregateRoot
class TodoList {

val logger: Logger = LoggerFactory.getLogger(this.javaClass)

@AggregateIdentifier
var uuid: UUID? = null

constructor()

constructor(uuid: UUID, name: String) {
logger.info("Creating todolist: " + uuid)
AggregateLifecycle.apply(TodoListCreatedEvent(uuid, name))
}

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

    fun updateName(uuid: UUID, name: String) {
logger.info("Updating todolist: " + uuid)
AggregateLifecycle.apply(TodoListNameUpdatedEvent(uuid, name))
}

}

I can see that i don't end up in the eventhandler for the creationEvent. I would have expected that that is. So i must be missing a piece?

Master Mind

unread,
Oct 15, 2016, 12:12:44 PM10/15/16
to Axon Framework Users
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
Message has been deleted

Geert Olaerts

unread,
Oct 15, 2016, 1:25:13 PM10/15/16
to Axon Framework Users
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?
Message has been deleted

Geert Olaerts

unread,
Oct 15, 2016, 2:25:04 PM10/15/16
to Axon Framework Users
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:
@Bean
public EventBus eventBus() {
return new SimpleEventBus();
}
But if i have it i get this warning 'Multiple beans of type EventBus found in application context: [eventBus, eventStore]. Chose eventBus'. But my events are not picked up in the TodoListService.

But even if i remove the eventbus and the service, i still can't get the eventsourcing to work due to '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.'

Allard Buijze

unread,
Oct 15, 2016, 3:27:12 PM10/15/16
to Axon Framework Users
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

--
You received this message because you are subscribed to the Google Groups "Axon Framework Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to axonframewor...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Geert Olaerts

unread,
Oct 16, 2016, 11:30:40 AM10/16/16
to Axon Framework Users
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:
@AggregateRoot
@Aggregate
class TodoList {

val logger: Logger = LoggerFactory.getLogger(this.javaClass)

@AggregateIdentifier
var uuid: UUID? = null

constructor()

    @CommandHandler
constructor(command: CreateTodoListCommand) {
logger.info("Creating todolist: " + command.uuid)
AggregateLifecycle.apply(TodoListCreatedEvent(command.uuid, command.name))
}

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

    @CommandHandler
fun updateName(command: UpdateTodoListNameCommand) {
logger.info("Updating todolist: " + command.uuid)
AggregateLifecycle.apply(TodoListNameUpdatedEvent(command.uuid, command.name))
}

}

When i now issue the CreateTodoListCommand, i can see the command being saved in mongo, so success so far. However when i try to issue the UpdateTodoListCommand, i still get the '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.' error.
It also doesn't break in the @EventSourcingHandler of the TodoListCreatedEvent, so that explains why the AggregateRoot doesn't have an AggregateIdentifier. Though I have no clue why that EventSourcingHandler isn't called?
I understand you saying that it is important that the first event sets the identifier, but i would have expected that it would be the one in the aggregate root that i annotated with @EventSourcingHandler that listens to the creation event.

Any more help would still be appreciated.

Allard Buijze

unread,
Oct 18, 2016, 3:12:43 PM10/18/16
to Axon Framework Users
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

Geert Olaerts

unread,
Oct 18, 2016, 3:33:49 PM10/18/16
to Axon Framework Users
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?

Allard Buijze

unread,
Oct 18, 2016, 3:39:15 PM10/18/16
to Axon Framework Users
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

Geert Olaerts

unread,
Oct 18, 2016, 3:50:05 PM10/18/16
to Axon Framework Users
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.

Geert Olaerts

unread,
Oct 18, 2016, 3:55:41 PM10/18/16
to Axon Framework Users
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.

Allard Buijze

unread,
Oct 24, 2016, 9:20:48 AM10/24/16
to Axon Framework Users
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

Geert Olaerts

unread,
Oct 24, 2016, 9:29:37 AM10/24/16
to Axon Framework Users
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/<id> 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.

Allard Buijze

unread,
Oct 24, 2016, 10:01:17 AM10/24/16
to Axon Framework Users
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

Geert Olaerts

unread,
Oct 24, 2016, 10:07:16 AM10/24/16
to Axon Framework Users
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

Geert Olaerts

unread,
Oct 24, 2016, 12:14:27 PM10/24/16
to Axon Framework Users
Removing spring devtools worked out nicely. Thanks, now i can continue playing around with it.
Reply all
Reply to author
Forward
0 new messages