Hi,
sorry for the late reaction. Due to some personal circumstances, I wan't able to respond any earlier.
When choosing aggregate boundaries, you also choose the consistency boundaries. Per definition. That means that if 100% consistency between two entities is important, they must belong to the same aggregate. If each customer needs an account, and an account must belong to exactly 1 customer, then they probably belong to a single aggregate. But beware, this may very well depend on the context. There might be more than 1 representation of a "Customer", if you have more than one context.
If entities belong to different aggregates, like your current situation with Customer and Account, you should use information in the query database. I don't agree with it being a bad practice. My simple rule is always: the aggregate should get all the information it needs to decide what to do. Some of that information is contained in the aggregate itself (because is logically belongs there), the rest should be provided via the command (method). Since commands (in Axon) are objects that are remote in many cases, I like to keep them small, and gather more information in the command handler, which passes all required information to the command method on the aggregate.
Do keep in mind that you don't really have "the" query model. Each target audience has one, the command side included, for cross-aggregate information sharing. For maintenance purposes, though, I personally let these components share a data-source. I don't like having 3 identical tables, just because three audiences need the same data.
I have had the discussion of the Repository.exists() method more than once. My counter-question is always: if a customer with a certain ID does not exist... how on earth did you get a hold of that ID? If it's a user-provided ID, you should always validate it against the query database. Usually, I use UUID's as aggregate identifiers, and prevent the users from entering them. They ususally provide a more human-friendly identifier, typically functionally defined.
The eventual consistency is really not as dangerous and complicated as it seems. In most scenario's, it means that the query database is a few millis behind. Of course you should consider what happens if things go wrong, but don't over-engineer it. You'll be fine. If you really won't be fine, then it's probably an indication that you chose the wrong aggregate boundaries.
In my workshops, I give hints on how to find aggregate boundaries: think of scenarios. What does a user want to do, and what are side effects? Typically, side-effects can easilly be eventually consistent. The user entering the data doesn't care about it.
Hope this helps.
Cheers,
Allard
Hi,
I am not able to write long emails at the moment. I will get back to you as soon as I can.
Cheers,
Allard