My application has a similar need to enforce uniqueness. I do it the old-fashioned way: with a constraint on a SQL table, either a completely standalone table that is maintained separately from Axon, or, more often, a table in the query model.
The pattern I end up using in several places is that when a request comes in from a client, I first insert skeleton/dummy rows into the query tables that represent the objects that need uniqueness guarantees. Those rows can be mostly null or dummy values, but contain the real values for all the columns that need to be unique. The tables have unique or primary key constraints on those columns. If one of the inserts fails, I roll back the transaction and return an error to the client telling them they used a duplicate ID. In your example, you could have a numeric user ID that serves as the aggregate ID, and also a username, and make the database enforce that both are unique.
If the inserts succeed, I dispatch commands to create any required aggregates in the usual way. Then later, when the aggregates publish “created” events, an event handler populates the rest of the data in the query tables. The event handler inserts a new row if there isn’t one already (to support rebuilding the query tables by replaying the event log) but in normal operation, it updates rows that were inserted before the aggregate creation commands were dispatched.
One could argue that this setup is not 100% pure event-sourced CQRS, but it is still pretty clean and solves the problem. I love aggregates and sagas but sometimes they’re not the right tool for the job.
Separating the aggregate identifier from the business-meaningful identifier is generally good advice too. In the user table example, with an aggregate identifier that is something other than the username, you can then easily support a user changing their username, and you won’t lose a single bit of the user’s history. That’d be pretty painful if the username were the aggregate identifier.
Of course, this is just how I solve the problem as a random Axon user. Perhaps other people approach it differently.
-Steve