Same surrogate ID for aggregates in different bounded contexts

The combination of “one monolith” with “several bounded contexts” doesn’t completely feel right, to be honest. When it comes to bounded contexts, you would typically have segregation of teams, repositories, databases, coding language, let alone what both those context’s their domain language (the messages) are.

It is from that point of view that Axon Server’s multi-context solution, through which you could design several contexts using the same infrastructure, segregates the message streams and event stores entirely. You wouldn’t want the traffic of another bounded context to flow over the same lane, as the concepts of context-one can completely deviate from context-two.

The above does not help you directly however, except for the fact that if you would use Axon Server Enterprise and define a Sales and Marketing context. Assuming you’re using the standard edition for the time being, I think it’s good to look at another solution.

The filterEventsByType field in the Aggregate annotation indeed would fit the bill, but wouldn’t work in combination with Axon Server. This feature stems from a migration requirement, where a user comes from an Axon 2 based application and moves up to the current version. This field was added as Axon 2 has a uniqueness constraint on the Event Store comprising of the aggregateIdentifier, sequenceNumber and aggregateType. As of Axon 3 (and thus Axon Server), the constraint is between the aggregateIdentifier and sequenceNumber only. So the filter would only work if you have a legacy, non-Axon Server event store (this should be documented more clearly though, my apologies for this info not being present).

Turning back to your original set of suggestions, this essentially gives you option 1 (look-up table in the controller) and option 2 (distinct event stores per context (actually what Axon Server Enterprise does). There is however a third option you could take. The uniqueness constraint is, as stated, on the aggregateIdentifier and the sequenceNumber. You wouldn’t really have any control over the sequenceNumber, but you do over the aggregateIdentifier. When storing an event, Axon will invoke the toString() method on the @AggregateIdentifier annotated field. Since you can define the type of the @AggregateIdentifier annotated field, you can thus control what the toString() method would return.

This would land you up with a solution where you’d introduce typed identifiers. To make this into the solution of your problem, the typed identifier class would not only return the (uu)id on the toString() invocation, but it should append the aggregate type to it. Doing so will ensure that whenever the framework loads your aggregate, it would use the TypedId#toString() method to check which aggregate to retrieve. Furthermore, you wouldn’t have any clashes anymore in the event store with this solution.

So, to round up, I actually think there are 4 options out there:

  1. Have distinct Event Stores per context, which you would likely want eventually any way. Axon Server Enterprise can help you with that.
  2. Used Typed Aggregate Identifiers which return a combined result of id+type on the toString(). Regardless of whether you take this route through customizing the toString() operation, I would recommend using a Typed Aggregate Identifier any how.
  3. Construct a custom look-up table solution. Would work, might be error prone to ensure this is as up-to-date as possible in the eventually consistent world you’re in. Dealing with set-based-validation, to ensure an identifier is not accidentally used twice, would likely be necessary in this case.
  4. Pretty straightforward, but simply not reuse the aggregate identifier.

I hope all of the above makes sense @Beka_Tsotsoria. And, that it clarifies what options you have. If anything from my comment is unclear, please do not hesitate to provide a response.