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