Having multiple instances work with the same event store isn’t any problem. It’s important to make sure each aggregate instance lives in only one place. If they are used concurrently in multiple nodes, you can get the ConcurrencyException, which effectively means that a command has not been executed against the last version of an aggregate. Most likely, your MetaData Routing strategy does not route consistently for a given aggregate. You’ll either need to make sure that a single aggregate is always routed to the same node, or that you perform a retry on a Concurrency Exception.
Regarding the transactions, in message driven applications (such as an Axon based one), it is important to see a single message processing as a single transaction. Thus, processing a command is a transaction. Processing an event is another (although it is sometimes possible to do it in 1). Therefore, you don’t “route” a transaction to another JVM, but you dispatch a command (which is handled atomically) and optionally react on its result. Your code should not rely on that command being executed in the same transaction, or even on the location where the processing happens.
You can attach the tenantID as meta data to the messages. That way, your infrastructure code will know about tenant ID, but the domain logic does not. At least, you’ll be able to transport your messages across JVM’s and still know for which tenant you’re executing code. You can use CommandHandlerInterceptors to set the necessary ThreadLocal data for the tenant configuration for JPA.
Cheers,
Allard