Modelling problem: aggregates with shared entities / 2-way associations

Hi,

I hope someone can help me with this problem. I’ve seen similar questions asked on the DDD/CQRS group, but the answers were rather obscure or zen-like.

My domain model seems to want 2 or more aggregate-roots (AR) to share the same entity instance (E)… or perhaps one instance each but representing the same real-world entity:

AR1 <-> E1
AR2 <-> E1

The association is also bidirectional: usually an AR asks E1 to do something, but sometimes a change to E1 will mean that AR1 and AR2 have to react in some way. I’m having trouble figuring out how to make this work in Axon.

(1) One possibility: move some logic out of AR and E into a separate command-handler or service, which then has to do lots of boilerplate code for looking things up in repositories. This feels like the entities(/ARs) becoming more anaemic.

(2) Another possibility: make E an aggregate-root in its own right. AR1 and AR2 just have the ID of E1, and E1 has a list of IDs {AR1, AR2}. But now AR and E need repositories injected into them, and they need to do lookups to get real instances to talk to… but people say they’re not supposed to do that because they would be reaching across into a different ‘bounded context’… or would they?

(3) Another idea: let AR1 and AR2 each have a separate copy of E1, and when AR1’s copy of E1 fires events, somehow arrange for AR2’s copy of E1 to apply the same events to keep the state in sync… but how to make that happen in Axon? Via some separate event-handler which then looks things up in repositories and issues commands to those things?

(4) Alternatively: I’ve seen some discussion that hints at ARs dispatching commands to other ARs as part of their own command-handling. Is that ok? It seems like a round-about way of just calling a method, but it does take care of finding the right object to call the method one (a lookup instead of an injection). This seems like an improvement on (2).

I’m aware of the rule that says you must not fire events from inside an event handler (although it seems like a reasonable thing to want to do). Does a similar restriction apply to dispatching commands from inside a command handler?

I guess my problem is that I’m used to doing entity/association modelling in the ORM/JPA world. I tend to think in terms of ‘real objects’ that “just” have references to other ‘real objects’ (ok, they get them injected really) and then simply call their methods to collaborate directly with them.

I haven’t seen a useful collection of patterns that show how to do similar things in the DDD/CQRS/ES world. People tend to say “just keep modelling your domain, you haven’t done it right yet” without really explaining how to do that in a suitable way… or what to do next if your modelling keeps coming up with the same awkward structures…

Anyway, all advice and suggestions gratefully received! I think I am going round in circles :wink:

Many thanks,
Richard

Hi Richard,

modeling for CQRS is a little more strict and requires some more choices to be made. By DDD definition, an aggregate cannot share an entity (instance) with another aggregate. If you would do that, AR1 could make changes in AR2, without AR2 knowing it. That would bypass the entire idea of an aggregate.

About the rule not to send events from an event handler: for event handlers inside an aggregate that is a very strict rule. Failure to comply will inevitable result in exceptions. The reason is that these event handlers are also invoked when the aggregate is reconstructed from past events. Applying a new event in one of those would cause the event to be re-applied each time the aggregate is loaded… Probably not what you want.

The best solution for this problem is to use a Saga. The Saga listens for activity in AR1 that might need to be reflected in AR2 (or vice versa). Check the reference guide to find out how Sagas work in Axon. If there is no “state” involved in a Saga, you can also implement a simple listener that listens for AR1 events and sends a command to AR2.

Dispatching new commands in a command handler is something I have heard before, but never seen yet. You could split an incoming command into 2 new commands and send those on the command bus. Just make sure you have a plan for the case where one command fails, while the other was successful. Loading 2 aggregates from the same command handler is not recommended if you ever plan to scale your application.

Hope this is a bit more concrete.
Cheers,

Allard

Hi Allard,

Thanks for the clarifications (and many, many thanks for Axon).

Actually my model is even worse than this :wink: - there are potentially whole trees of relationships, where changes can ripple up or down the trees. Although thankfully the trees will not be more than 3 or 4 levels deep.

I am pinning my hopes on commands-within-commands. The initial command causes the right aggregate-root to be loaded (it’s probably cached already), then for whatever reason some consequences of that command need to be propagated to other ARs (of the same or other types), so the first AR issues suitable “secondary commands” to propagate the changes.

The first AR uses CommandGateway to dispatch its secondary commands, so there is relatively little boilerplate code in the ARs, and the job of loading and dispatching to the right secondary ARs is still handled uniformly for all commands. The relevant secondary ARs get “dragged into” the same transaction that was created for the first command. And the secondary ARs don’t need magic knowledge of who sent the command or the fact that command they are handling is “secondary”.

A couple of potential issues…

  • If the dispatch of a secondary command fails, does that happen inside a nested transaction? It might not matter, as long as the second command failure always causes the primary command to fail as well.
  • To avoid slowdowns when propagating changes up or down a tree, caching repositories would be essential. But since these use pessimistic locking, we could easily get a deadlock if there is any loop in the AR associations. Maybe I’d need to extend the repository behaviour to include a check for whether the current transaction already has a lock on the requested AR (or does it already do this?).
  • We could also get loops in command dispatching, similar to event-firing loops, that we’d have to be careful to avoid.

It seems like this approach might give us a way to model tight associations, even bidirectional associations, between various types of AR without “breaking the rules”. Am I on the right track, or is it all going to end in tears…? :wink:

Thanks again,
Richard

Hi Richard,

as long as you use the SimpleEventBus, all nested commands are essentially running in the same transaction. Locks are aware of the fact that a thread may already hold a lock. In that case, they maintain a counter, releasing the lock only when the counter is back at 0. Essentially, it’s a ReentrantLock.

Make sure to properly fail the first command when the nested command fails. But if the nested command succeeds and the outer fails for whatever reason…

I haven’t been down the road of nested commands yet. So honestly, I couldn’t tell you if it’s all going to end in tears. I normally go for the Saga approach myself (after doing a remodelling attempt to reduce dependencies).

Cheers,

Allard

What’s the best way to get a CommandGateway injected into the @CommandHandler methods of my aggregate-root/entities? The documentation says that additional parameters to @EventHandler methods will be given matching beans from the Spring context, but it doesn’t mention similar behaviour for command handlers.

I guess I could add a CommandGateway parameter to my creation and snapshot event handler methods, but that feels a bit sneaky…

Thanks,Richard

Hi,

on the @CommandHandler methods, it works exactly the same way. You don’t want to do this in your aggregate’s @EventHandler methods, as you would also trigger it when loading the aggregate.

Cheers,

Allard

Awesome, thank you. And thanks again for your help!

Cheers,
Richard

So, it does in fact end in tears :wink:

If any nested command fails, the top-level surrounding Spring transaction is immediately marked for rollback. So the outer command handler can’t gracefully recover from an inner command failing: when it eventually tries to commit, the commit fails. Allard, you hinted at this, but I didn’t realise the full significance of it - doh!

In my particular case this isn’t the end of the world, because my outer command handlers aren’t aggregate-roots, so I just need to make them into plain old services instead.

Cheers,
Richard