Repository locking strategy and aggregate versioning

On our project we use the default optimistic locking strategy to
manage conflicting changes to aggregates. In version 0.6 the
Repository.load(identifier) was deprecated in favour of
Repository.load(identifier, version). This deprecation has since been
remove in 0.7.

When we change an aggregate e.g. to set up an association through a
reference to another aggregates AggregateIdentifier our command passes
through the verison of the aggregate that the client app knows about
and we load it from the repository using the version and change the
domain as appropriate. The problem is that the locking strategy can be
completely bypassed by forgetting to use the aggregate version when
loading as aggregate i.e. calling Repository.load(identifier) instead
of Repository.load(identifier, version).

I think it would make sense to change the Repository implemetation so
that if a locking strategy is configured the aggregate returned from
the Repository.load(identifier) cannot be change i.e. you cannot call
the apply() method on the aggregate.

Hi Sheak,

the locking strategy in the repository is slightly different from the optimistic strategy you can achieve by supplying the version-known-by-client.

What the repository does is the following:

  • two commands load an aggregate simultaneously. If the strategy is optimistic, both threads will recieve a copy of the aggregate.
  • thread 1 makes a change, and stores the aggregate, changing it to V+1
  • thread 2 now makes a change and stores is too. Since it has based the changes on V, the repository will throw a ConcurrencyException. The command may be retried.
    If the strategy was pessimistic, thread 2 would hold at the repository.load(), until thread 1 has made the changes.
    The locking strategy in the repository is just a way to guarantee that all changes are applied sequentially on every aggregate.

The concurrency control by using the version supplied by the client is a little different. It is of a more functional nature. What do you want your application to do when an aggregate has been changes by another process while you were looking at your screen, taking a decision?

Here is a scenario:

  • user 1 loads a screen showing an aggregate V=0
  • user 2 also looks at that screen
  • user 1 sends a command to it. Since there are no concurrent modifications (yet) it is changed to V=1
  • user 2 also makes a change to modify it.
    Now, two things can happen. First, let’s assume we don’t have any conflict resolvers.
  • the command given by user results in a ConflictingModificationException when it tries to load the aggregate, since it expects to execute a command on V=0, but was 1.
    The other scenario is when you do have a conflict resolver applied.
  • the command will load the aggregate, but will internally mark it as “potential conflict”
  • the command will apply changes on the aggregate, changing it from V=1 to V=2.
  • before committing the aggregate back to the repository, it will verify that the “unseen” changes V=0 to V=1 do not conflict with the newly applied changes V=1 to V=2. The conflict resolver makes this decision.

As you can see the two mechanisms serve a rather different purpose. The locking mechanism of the repository being purely technical (making sure changes are applied sequentally at all times), and the client-known-version mechanism is more functional: “do you want a user to be notified when another user did something he hasn’t seen?”.

Usually, a question if this sort is a good indication that something isn’t clear in Axon. How can I make this distiction in mechanisms more clear? Would it help if I were to change the name of the mechanism in the repository to “ConcurrencyControl”, or something similar?

Cheers,

Allard

Hi Allard,

Thanks for clearing that up. I understand the difference now. I guess my question relates to the functional optimistic lock exception based on the aggregate version I want a way to enforce that you cannot change an aggregate unless you load it from the repo using the version

Hi Shea,

I don’t see a way to enforce this from within the Axon Framework (without creating complex configuration options).

You could create given-when-then tests that show that version conflicts are correctly reported. If a command handler uses the wrong method on the repository, you’ll have a failing test.

Another thing you could do within your project is create an aspect around the repositories (using Spring AOP for example) that throws an exception when the versionless-load method is invoked. Another way is to create a delegate implementation of the interface that throws an exception when that method is invoked, while forwarding other invocations to the delegated instance.

Cheers,

Allard