Probably a stupid question(s) re @AggregateVersion

OK, maybe not an entirely stupid question given I’ve only been looking at Axon < 1 week. But then again…

At the moment I’m trying to get my head around how optimistic locking / conflict resolution would work in practical terms but the documentation is a little lacking in this regard, so…

I’m hoping someone can explain the practical usage of the @AggregateVersion annotation. I’ve been doing some experimenting and can see that the AggregateLifecycle keeps track of the version based on applied events. I have access to this from anywhere inside the Aggregate instance and can easily look it up when required, so from an aggregate lifecycle perspective the adoption of an annotated field feels somewhat redundant.

My next thought was in regards to how the field was actually being set, I had naively assumed the framework would set the @AggregateVersion field value as part of the event publishing mechainsm, or, once an aggregate had been fully hydrated (after initializing), but this is not the case and it seems I would be responsible for setting this field. As far as I can tell this would involve:-

  • Incrementing the field on each invocation of an @EventSourcingHandler annotated method
  • Adding an annotated @SequenceNumber parameter to each @EventSourcingHandler method

This means having to add the same line of code to each handler method to update the aggregates version field. Neither method is ideal and both error prone, e.g. developers forgetting to code for it.

So, to the point of the post:-

  • In terms of using a version for optimistic locking, why bother with an @AggregateVersion field at all when the aggregate instance can simpy get it from the AggregateLifecycle?
  • Is there an easier, more reliable way of setting it, as opposed to having to do it manually in @EventSourcingHandler methods?
  • Exactly how and when does the framework use the annotated field, if at all?

Thanks in advance, Andy.

Exactly Andy, there are no stupid questions!
You’ve just hit a subject not that many people use, to be honest.

The Aggregate Versioning logic has existed since Axon Framework 2, about nine years ago.
We’ve had limited requests on how to use this within that time frame.
This, combined with the fact that nine years ago, there was just one primary maintainer (AxonIQ’s CTO nowadays), documentation is definitely lacking.

For this, I’d like to extend my apologies.
If you want to file an issue for it with our Reference Guide, that would be amazing.
Of course, if you have some spare time and interest in doing so.

Now, let me go to your questions regarding the versioning support Axon Framework provides:

In terms of using a version for optimistic locking, why bother with an @AggregateVersion field at all when the aggregate instance can simpy get it from the AggregateLifecycle?

What the version returns depends on whether you’re Event Sourcing your Aggregate.
If you are not, the user is expected to define the version through the @AggregateVersion annotation, which they can put on a field or method.
If you are using Event Sourcing (the default in Axon Framework), the sequence number is indeed used, with no direct option to change this.

In short, as you’re event sourcing the Aggregate, you don’t have to use the @AggregateVersion annotation.

Is there an easier, more reliable way of setting it, as opposed to having to do it manually in @EventSourcingHandler methods?

As the annotation isn’t necessary for your application, this question might not be overly significant.
I will give a response regardless, though.

There is no easy way to invoke some task on every event, apart from having an Event Sourcing Handler for all Aggregate events.
There is a way to make a generic event handler, which is only invoked when there is no more specific event handler for the event in question.

To make it more concrete:

class MyAggregate {

    // Some state, the aggregate id, and command handlers left out

    public void on(BazEvent event) {
        // update specific state

    public void on(Object event) {
        // update generic state like version

class FooEvent { }
class BarEvent { }
class BazEvent { }

If the MyAggregate published foo, bar, and baz, and the Aggregate is event-sourced:

  • FooEvent is only handled by the least specific handler on(Object)
  • BarEvent is only handled by the least specific handler on(Object)
  • BazEvent is only handled by the more specific handler on(BazEvent)

With this, you can thus have one catch-remaining-event-handler.
That way, you wouldn’t have to write an EventSourcingHandler for every event if you’re not interested in the rest of the event.

Exactly how and when does the framework use the annotated field, if at all?

Axon Framework will thus only use the annotated field if you have a State-Stored Aggregate.

However, the aggregate version may be used regardless of the @AggregateVersion annotated field.
More specifically, Axon Framework will use it to compare with the @TargetAggregateVersion field within a command.

Hence, you can make a command like so:

class SomeCommand {
    private final UUID aggregateId;
    private final long aggregateVersion;

    // left out rest of state, getters, constructor, equals, and hashcode

By dispatching a command like SomeCommand, the field annotated with @TargetAggregateVersion is used to validate if the Aggregate the command is targeted towards is still on that version.
If it is not, a ConflictingAggregateVersionException will be thrown.

Well, quite a lengthy response here.
I do hope everything makes sense here, @andye2004!
Be sure to fire additional questions if necessary. :+1:

@Steven_van_Beelen, Many thanks for taking the time to answer so thouroughly! What you basically described is pretty much what I had suspected other than the use of the @AggregateVersion in state-stored aggregates, but as you rightly pointed out, this is not a concern for me. Also, please, there is no need to apologise for the lack of documentation, unlike a lot of OSS, Axon seems very well documented to me.

Given your very thourough answer I think I understand things from an optimistic locking perspective now, however I am still a little fuzzy on the conflict resolution side. Supposing I annotate a command as follows:-

class SomeCommand {
    // ...
    private final long aggregateVersion;
    // ...

And assuming the framework detects one or more unseen changes that I want a chance to resolve before the ConflictingAggregateVersionException is thrown. Is it the case that I add an instance of ConflictResolver to the command handler method signature (as below) and the framework will populate it with all unseen events and allow me the possiblity of resolving things in the command handler, and only throwing an exception if there is actually conflicting changes being applied?

class SomeAggregate {
    // ...
    public void doSomething(SomeCommand cmd, MyConflictResolver resolver) {
        // Check if resolver is populated with events
        // if it is, pass some apropriate predicates for resolution
        // write code as normal and it will be processed if no exception
        // is thrown from the resolver
    // ...

Have I got this correct?

Thanks again, Andy.

Thanks for the kind words, Andy. :slight_smile:

I did some additional checks in the implementation of the framework, and noted I shared some assumption in my previous post. The ConflictingAggregateVersionException is thrown if the CommandBus used is the DisruptorCommandBus, as this command bus uses a custom variant of the aggregate Repository that involves several layers of caches. In those cases, the version deduces whether a cache hit or miss is in place.

However, I take it you’re not using the DisruptorCommandBus (note this is useful very high command loads only). Hence, I expect that you wouldn’t hit the ConflictingAggregateVersionException when using the @TargetAggregateVersion annotation in your commands.

Putting that aside, your description seems correct to me. The ConflictResolver, when you invoke the detectConflicts operation, will retrieve the expected set of events based on the aggregateIdentifier and aggregateVersion, and the actual events based on the current aggregateVersion. The Predicate you provide will in turn allow you to detect whether there are conflicts.

1 Like