We have a scenario that would like to use the same identifier value, but on two different aggregate types.
i.e. for example
T1.id = "XYZ"
T2.id = "XYZ"
but T1, T2 is different aggregate types.
In the event store implementation and aggregate initiailzation, the framework assumes that no two types of aggregates share the same ID. This finally lead us to "Aggregate identifier must be non-null" exception, during loading one aggregate, say T2, because T1's event stream being consumed by T2 during loading/initialization, which of course T2's identifier would not be set after consuming the events from T1.
It sounds illogical to prevent us to have two different concepts of things but shares the same ID.
Is it intentional? How to work-around it? Thanks a lot.
Hi @kmtong, I'd like to start off with a house hold point. This is the issue tracker, meant for issues found in the framework or suggestions to be added. So not so much for usage questions. For that, I'd like to refer you to StackOverflow with the axon tag or our Google User Group. Due to this, I will be closing your question immediately after my comment.
Now to answer your question around different Aggregate implementations with the same Aggregate Identifier: we typically suggest to use a UUID as the Aggregate Identifier. Thus should give you a unique id every time around, thus removing the problem at hand. If you for one reason or another have to stick with your current ID creation process, then I'd suggest the following.
The framework internally calls the toString() function on the ID object you provide. If you thus create a typed Aggregate Id for every different Aggregate implementation you have, you could override the default toString() function in there. Then, you could append the actual ID you set with the Aggregate name in that function. That way, from an Axon perspective, you have unique ID's, whilst you'd still use the same identifier.
I hope this gives you the required insights @kmtong!
Thanks for your response.
This issue seems like a question, but in fact it is not. It undermines a deeper design issue. The current design prohibits us from selecting what we think to be the best identifer in the domain, and certainly the current design assumption is not desirable for our situation.
As it is a deep design issue, and I think it deserves a deeper discussion.
The user group typically contains longer discussions then just a one-off response to a question too.
So I'd think that's still a fair platform to move any future request you might have. :-)
What would be even better, is to have FAQ/Wiki's on how to approach things like this.
At AxonIQ we're planning to provide how-to's in the near future. I think this question you've posed would be an excellent candidate for that. What do you think @kmtong?
Thank you for the workaround method for using the same id. We will try it for the time being. And we hope there would be a better solution ultimately.
Ok anywhere at your convenience.
For me this a bug in Axon. A bug caused by design/choice but a bug, or at least a serious limitation.
I have different Aggreagtes that should have the same identifier. It can be a common case :
Order aggregate at a moment emits a OrderPaid event. This event can be handled, and create a new aggregate of type OrderDelivery. The two aggregates have the same identifier : that of the order. I especially do not want to generate another identifier for the deliveryUser aggregate is created, a UserCreated event is emited, and an event handler create immediately a new aggregate UserRights that manages rights of this user. The two aggregates have the same identifier, that of the user. And the UserRights aggregate emits events like UserGrantedOnFeature and UserRevokedFromFeature.I do not see any reason, other than an opinionated choice, to forbid two aggregates of different type from having the same identifier. While it is possible to allow that, just by changing unique index of the DomainEventEntry table by adding type field to already present fields aggregateIdentifier and sequenceNumber.
Axon should not force such design, but stay agnostic.
Firstly, @fredgate, I feel that your comment is coming over as quite offensive towards the entirety of the Axon team. I dearly hope this is a wrong assumption from my part, but I felt you should take this as a personal note regardless.
A part from that, the reasoning for having this as you call it "serious limitation" is due too the unique constraint on the aggregateIdentifier and sequenceNumber pair in the Event Store. Adding the aggregateType to this as you're suggestion will make this check more costly, at all times, for every user.
Additionally, the situation you sketch can again easily be solved through other means than reusing the aggregate identifier is you're suggesting. Thus your statements like "The two aggregates have the same identifier" and "especially do not want to generate another identifier" are just as much design decisions you can debate. The desired association you want between both aggregates is still very easily achieved - reusing the Aggregate Identifier as that association as the choice you have made.
You can, like your suggesting go with a specific DomainEventEntry, or hide away this ID reuse by providing your own EventMessage with an overridden getAggregateIdentifier() function which adds the Aggregate type. Or the typed Aggregate Identifier solution I am suggestion could resolve the problem you're encountering, which would constitute in a UserId class and UserRightsId class for example - both could than contain the same identifier, the toString() function will than serve the purpose of generating a unique ID by appending the aggregate type for you.
If you still feel this should be a feature in the framework, your are more than welcome to provide an issue and pull request for them. This is an Open Source framework after all, where everybody can contribute too.
You could always have an AggregateIdentifier that is different than the domain specific UUID
Something like AxonIdentifier(schema:String, id: UUID) {} that can generate on toString:
"{schema}://{uuid}"
leaving your id's like: "userRights://{uuid}" / "user://{uuid}"
Most helpful comment
Thanks for your response.
This issue seems like a question, but in fact it is not. It undermines a deeper design issue. The current design prohibits us from selecting what we think to be the best identifer in the domain, and certainly the current design assumption is not desirable for our situation.
As it is a deep design issue, and I think it deserves a deeper discussion.