How to create an aggregate without id?

The documentation clearly states that it is possible:

Commands that create an Aggregate instance do not need to identify the target aggregate identifier, as there is no Aggregate in existence yet. It is nonetheless recommended for consistency to annotate the Aggregate Identifier on them as well.

The point of that is to use JPA with @GeneratedValue (older post here).
Three problems:

  1. If I omit the id field entirely from the command, then dispatch fails with “no routing key found”
  2. If I have the id field but leave it empty, then this happens:
    a) it lets that command through which seems weird (GitHub issue)
    b) when the entity manager persists this entity with id = null, it naturally calls the SQL sequence automatically
    c) command gateway returns null. This is because an instance is created first, then the id is resolved, and only after save on the repository is called.
  3. CommandTargetResolver is completely ignored for constructor command handlers, so my custom resolver that calls SQL sequence directly is not invoked.

Hello Sam,

The command handler on a constructor is a specific case of command handler to simpify the use case of creating an aggregate.

Normally you would need to introduce separate command handler that would create an instance of aggregate and then it save it, axon simplifies this process by letting you do the thing inside class constructor of an aggregate.

There is however, an solution to your issue, if I’m not mistaken, you want to create an aggregate identifier based on some database id generator, and by the point in time when you publish such command you do not have access to the ORM/Repository implementation.
In axon, there’s a thing https://github.com/AxonFramework/AxonFramework/blob/master/messaging/src/main/java/org/axonframework/commandhandling/distributed/UnresolvedRoutingKeyPolicy.java which controls how to route the command when there is no aggregate identifier, there was also a pull request for configuring such here: https://github.com/AxonFramework/AxonFramework/pull/456.
Using above classes and configuration, you could create a command that is dispatched to static command handler which behavior would be like this:
You create command class for static dispatch with either defined aggregate identifier as static one, or you configure the unresolved key policy, whatever you choose, it will work.
Now, in your constructor of your aggregate, instead of passing / persisting the id specified in the command, you generate that with your repository id generator.

However it really smells like a design smell. The aggregate command handler itself should not really have a knowledge of persistence, nothing should in your core domain, unless really necessary, an ideal way would be to create command handler for creating your aggregate outside of your aggregate and call it an application service, that would just listen for commands and create appropriate aggregates for you depending on how you would like to create the ids. You are adding dependency of your external write model to the aggregate itself which for me just doesn’t seem right.

To summarize, yes it is possible, however it is not in my opinion the place where aggregate ids should be passed, generated etc, i think that you’re trying to make your life easier with integrating your external database with the axon infrastructure. In my opinion axon has its own persistence model which should not be modified to our needs, but the other way around, our needs should be modified in favor of axon.

An example:

`

@Component
class AggregateCreator {
    @Autowired
    private lateinit var aggregateRepository: Repository<Aggregate>

    @CommandHandler
    fun handle(cmd: CreateHotWallet, identifierFactory: IdentifierFactory) {
        val createCommand = CreateHotWallet(identifierFactory.generateIdentifier())
        this.aggregateRepository.newInstance { Aggregate(createCommand) }
    }
}

`

Excuse me, an edit:

It is not possible as you want it exactly, but it is possible to solve your solution in slightly different way by not touching how axon works.

Thanks, Robert!

I saw the routing policy in the sources, it only can give a random id based on AtomicLong or throw an exception, which is not suitable for me.
Dismissing a command to create another one in the handler indeed smells a bit, so also pass.
Creating an aggregate in a separate handler class seems interesting, I have not got to that part yet, maybe that will be better than my current solution (what do you think about that?).

Your reply has been helpful!

Although it is not entirely clear why Axon can’t just use my CommandTargetResolver if there is no routing key. Would totally solve this problem.

I noticed I can override the routing strategy, it works, but the cleanest solution so far is to override the command gateway:

API definition