Hello,
this may not be an Axon-specific question, but I’ve decided to post it here anyway.
Short version
Is it possible for event-sourced aggregates, from different bounded contexts, to have the same surrogate ID in the same EventStore?
Longer version
Let’s say I have 2 Product aggregates one in Marketing and the other in Sales bounded context which are described by a different set of attributes and business rules. Conceptually Product lifecycle is owned by the Marketing context and when it is created there, the corresponding aggregate in the Sales context is also created automatically. There is also an API layer on top of these bounded contexts for uniform access.
For the full picture, let’s consider the following example:
- API client invokes
POST /products
. API layer generates IDX
for the Product aggregate, dispatches theCreateProduct
command to the Marketing context and returnsX
back in the response. - When
CreateProduct
command is handled in the Marketing context,ProductCreated
event is published, to which Sales context reacts with creating a Product in the Sales context too (= publishesProductCreatedInSales
). - API client then wants to assign price to the product by invoking
PUT /products/X/price
, which in turn dispatchesAssignPriceToProduct
command to Product in Sales context, withX
as the target aggregate identifier.
-
In the 2-nd step above, if Product in the Sales context is created with the same ID
X
as the ID of the Product from the Marketing context, then I get the following exception when handlingAssignPriceToProduct
command:org.axonframework.eventsourcing.IncompatibleAggregateException: Aggregate identifier must be non-null after applying an event. Make sure the aggregate identifier is initialized at the latest when handling the creation event.
I believe it’s because,
ProductCreated
event (from the Marketing context), with IDX
is loaded from the event store, but it is not actually applied to Sales Product because event(-sourcing) handler does not expect theProductCreated
type (but rather aProductCreatedInSales
). Which is expected behavior. -
If Product in the Sales context is created with a different ID
Y
, then AxonFramework won’t be able to route theAssignPriceToProduct
command to the aggregate with target identifierX
(which is the only ID known to the API client).
So far I see the following options to address this issue:
- Let these 2 aggregates have different surrogate IDs and let the API layer maintain the lookup table/cache for translating one to another before dispatching the commands. This can be achieved if API will listen to
ProductCreatedInSales
event which can contain both IDs, update the lookup table which can be queried later on by customCommandTargetResolver
implementation. But this seems to be a bit complicated solution. - Let these 2 aggregates have the same surrogate IDs, but store them in different instances of EventStores (or different Contexts in case of AxonServer). This brings additional pain because my application is designed to be a monolith (at least initially).
- Do not create a Product in the Sales context automatically, let the client explicitly create it with a different ID, which will be known for
AssignPriceToProduct
command routing. This would effectively mean API per Bounded Context, which I’d like to avoid, because in reality this API is Backend-for-Frontend, and there is only one notion of Product in the UI, which should be created only once.
I have a feeling that I’m missing something simple. Either in the modeling/approach or in the implementation/AxonFramework. And surprisingly I couldn’t manage to find any useful resource on that matter. Your directions will be much appreciated.