EventStore doesn't take aggregate type into account

Hi,

I’m trying to migrate from Axon 2.4.5 to Axon 3.4

Old EventStore API uses the aggregate type.

New EventStore doesn’t take aggregate type into account.

So, events for aggregates of different types but with the same aggregate identifier are mixed.

E.g. aggregates Client and Performer with the aggregate identifier “Unknown”.
On loading any of them clientAggregateRepository.load(“Unknown”) tries to reconstruct Client with both event types ClientEvent and PerformerEvent. Because events are read just by id.
And the “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.” is the natural result.

What am I doing wrong?

@Bean fun eventStorageEngine(mongoTemplate: MongoTemplate): EventStorageEngine = MongoEventStorageEngine(JacksonSerializer(createMapper()), null, mongoTemplate, DocumentPerEventStorageStrategy())

@Bean fun eventStore(eventStorageEngine: EventStorageEngine): EventStore = EmbeddedEventStore(eventStorageEngine)

@Bean fun clientAggregateRepository(eventStore: EventStore): AggregateRepo = EventSourcingRepository(Client::class.java, eventStore)

@Bean fun performerAggregateRepository(eventStore: EventStore): AggregateRepo = EventSourcingRepository(Performer::class.java, eventStore)

Should some filter be applied to events somewhere?

Thanks,
Viktor

Hi Viktor,

You are not doing anything wrong in this case.
Axon 2 to 3 has seen the change of removing the ‘aggregate-type’ constraint on the Event Messages, as you’ve experienced.

To resolve the problem where it is required to use a reoccurring aggregate identifier between different aggregates (instead of for example a UUID), we typically suggest to prepend the aggregate identifier with the Aggregate type in the toString() function of the @AggregateIdentifier annotated field within your Aggregate.
The toString() function is what the framework uses to drive the aggregate-id column in your domain event store, hence why this resolves the problem.
In this scenario it’s easier to have a typed ID for your aggregates, so something along the lines of a PerformerId and ClientId object.

In your sample, you’d thus get something like a ‘performer-{aggregate-id}’ for your Performer aggregate, and similarly making that resolution for your Client aggregate.
This will allow you to keep using identical aggregate identifiers for your messages.

In your case however, you will need to do something about your existing events as well.
The most pragmatic solution to this end, is to actual perform an update to your event store, to change all the aggregate ids to be prepended with the aggregate type.

I hope this gives you the needed guidance Viktor.
As always, feel free to post follow up questions if you do no feel this will resolve your problem.

On an additional note, we have recently (end of October) released Axon 4. The upgrade from 3 to 4 will not impose any event store table deviates, just import changes.
If you are on route to make a migration to Axon 3, I’d strongly recommend moving to 4 right there after.

Cheers,
Steven

Hi Steven,

Thanks a lot!

I considered such solution as a workaround.
But I could not imagine that the reality is so sad. The reasons for this change are unclear.
It forces to use complex IDs instead of just Strings. Or wrap repositories and/or aggregates to some helpers.
Migration becomes much more difficult.

Is it possible to introduce/revert to EventStore default methods with aggregateType as parameter?
This at least will allow to implement and use more more convenient event store.

Best Regards,
Viktor

Hi Viktor,

this is a decision that we have made quite a while ago, and I remember there was a lot of discussion about the backwards incompatibility that this would introduce. While I don’t remember all the details, there were a few factors that finally drove us to implement the change:

  • the best practice for aggregate identifiers was to use globally unique, preferably random, values, such as UUIDs.
  • the added complexity of managing and passing aggregate types in different locations
  • the increased size of indices in the event store (one highly repetitive value in the index could be removed)

While we do regret that this complicates migrations in some cases, there is a workaround for it.
The EventSourcingRepository declares a protected method called “readEvents(String)”, which you can override to change the behavior. In your case, you could override this method to ignore any Events in the stream that do not represent the current aggregate type. In Axon 3.3 (and later), the DomainEventStream has a .filter(Predicate) method that allows you to easily do this.

An implementation could look like this:

@Override
protected DomainEventStream readEvents(String aggregateIdentifier) {
    return super.readEvents(aggregateIdentifier).filter(m -> aggregateModel().type().equals(m.getType()));
}

Note that to keep the current semantics, you would have to retain the unique key on the combination of sequenceNumber, aggregateIdentifier as well as aggregateType in you event store table.

I hope this helps.
Cheers,

Allard

Hi Allard,

Thanks,
it looks like the code suggested by you works. At least all our integration tests are OK.

Best Regards,
Viktor