AF5: Migration of routingKey for @Command

Hello,
While migrating our applications from AF4 to AF5, I stumbled upon the following topic:

Here is an example of a command in AF4:

data class CreateEquipmentCommand(
    @TargetAggregateIdentifier
    val equipmentId: EquipmentId,
    val equipmentNumber: EquipmentNumber,
    val customerId: CustomerId,
    val name: String? = null,
    val equipmentCode: EquipmentCode
)

The Axon Migration Tool turns this into the following for AF5:

@Command
data class CreateEquipmentCommand(
    @TargetEntityId
    val equipmentId: EquipmentId,
    val equipmentNumber: EquipmentNumber,
    val customerId: CustomerId,
    val name: String? = null,
    val equipmentCode: EquipmentCode
)

Essentially, only @TargetAggregateIdentifier is replaced by @TargetEntityId. The command works fine, too. All tests passed (after various other adjustments).

However, through a special integration test, the following became apparent:
The test simulates massively parallel command processing by sending commands with the same TargetEntityId in parallel within coroutines. With AF5, the commands are genuinely processed in parallel, but also across multiple CommandHandler instances. This broke the validation based on state that was implemented in the command handler.

In AF5, you have to use the routingKey attribute on @Command so that commands with the same TargetEntityId are also dispatched “one after another”.

In AF4, you could rely on commands with the same TargetAggregateIdentifier always going through the same CommandHandler instance, which allowed you to perform validations per aggregate.

This means the command above has to look like this in order to behave the same way as in AF4:

@Command(routingKey = "equipmentId")
data class CreateEquipmentCommand(
    @TargetEntityId
    val equipmentId: EquipmentId,
    val equipmentNumber: EquipmentNumber,
    val customerId: CustomerId,
    val name: String? = null,
    val equipmentCode: EquipmentCode
)

I understand the intention behind the design in AF5; it offers a lot of flexibility in how I build my commands and how they are dispatched. When migrating an AF4 application, however, this subtlety isn’t immediately obvious, because routingKey is optional and all unit tests will run, I assume. You only notice the problem once you have genuinely parallel command processing in tests or in production.

Question: Shouldn’t the Axon Migration Tool perhaps generate routingKey annotations on the commands? This would make things easier, as we have hundrets of commands.

Klaus

Your suggestion would be a nice idea, @klauss42 .

Granted, we could also still have a fallback to check for the @TargetEntityId and make it the routingKey if there is (1) no @Command annotation or (2) the routingKey property isn’t set on the @Command annotation.

I am honestly fine either way! Would you be up for a PR? With LLMs nowadays, that shouldn’t be to much work to help us out. :slight_smile:

Hi Klaus,

thanks for flagging this one. And yes, I think bottom line is that indeed, the @TargetAggregateIdentifier in AF4 is equivalent to having both the @TargetEntityId and the @Command(routingKey = “…“) specified.

We’ll adjust our migration tools accordingly.
Just to confirm, did you use the Agent Skills to perform the migration?

// EDIT - Steven’s reply wasn’t rendered until I sent my reply.

Steven, Allard,
I already fixed this in the migration tool and am just preparing a PR

Klaus