this may not be an Axon-specific question, but I’ve decided to post it here anyway.
Is it possible for event-sourced aggregates, from different bounded contexts, to have the same surrogate ID in the same EventStore?
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 ID
Xfor the Product aggregate, dispatches the
CreateProductcommand to the Marketing context and returns
Xback in the response.
CreateProductcommand is handled in the Marketing context,
ProductCreatedevent is published, to which Sales context reacts with creating a Product in the Sales context too (= publishes
- API client then wants to assign price to the product by invoking
PUT /products/X/price, which in turn dispatches
AssignPriceToProductcommand to Product in Sales context, with
Xas the target aggregate identifier.
In the 2-nd step above, if Product in the Sales context is created with the same ID
Xas the ID of the Product from the Marketing context, then I get the following exception when handling
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,
ProductCreatedevent (from the Marketing context), with ID
Xis loaded from the event store, but it is not actually applied to Sales Product because event(-sourcing) handler does not expect the
ProductCreatedtype (but rather a
ProductCreatedInSales). 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 the
AssignPriceToProductcommand to the aggregate with target identifier
X(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
ProductCreatedInSalesevent which can contain both IDs, update the lookup table which can be queried later on by custom
CommandTargetResolverimplementation. 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
AssignPriceToProductcommand 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.