Ok, so you have two event sourced aggregate roots. You need to call a method in one from the other. How do you do it?
If you find your self asking this question, don’t worry, your not alone. It comes up a lot.
The short answer – it suggests there could be something wrong with your aggregate root (AR) boundaries.
Given an AR defines a consistency boundary, you can call methods on objects within the AR without any problem. This is because you will hold all the necessary references. If you try to call a method on another AR, then you can’t guarantee consistency between them.
Solution 1 – Re-define your Aggregate Roots
Deciding what is in, and out of your AR is a process of discovery. Rarely will you get this right at the start. What is important however,
is your design remain loosely coupled and by implication easy to change.
This is also why I prefer state based testing on my domain, rather than interaction testing. Moving functionality around should have very little impact on your tests. In many cases, through re-defining your AR’s this inter aggregate communication problem goes away.
Solution 2 – Use a Process Manager (or SAGA if you prefer)
Assuming you have decided that the design is right. And you still need to communicate between AR’s, then you can fall back on a process manager. In the normal flow of a CQRS ES application, some one or something raises a command. The router passes the command to a handler. The handler calls up the AR. It then issues the command. On success the handler persists the events and publishes them to the bus. There will be times when the system may need to do other things when certain events happen. This is when a process manager may be appropriate.
A process manager listens for specific events and if in the correct state, issues commands.
This is a rather smart form of inter aggregate communication.
It’s smart because it decouples how the process works. Take an order processing system as an example. Your design may have come up with a distribution, billing and reporting classes. If at a later point, in a non CQRS and Event Sourced application, you add a new warehouse class, you would need to change the order of communication. Without a process manager, that may have required changes to all three other class. The beauty of a process manager is that it promotes the right sort of code re-use. Your code becomes easier to update when responding to changes in the business.
Solution 3 – Change you Perspective
It may seem like you need to talk to the other aggregate to get your process done. Rather than reaching for a process manager, ask your self 1 simple question. When generating your command, was the extra data you needed available in the read model?
The read model is your eventually consistent store of read optimised data. You may often find the data you need is available. Include this data in your command and you remove the need for inter-aggregate communication. I call these ‘fat commands’.
Solution 4 – Inject a Service
Here is my dirty little secret. I have used this approach and it works fine but I’m not sure it is a good thing to do. So try to minimise it’s use.
The idea is to define a service within your domain that is responsible for looking up data. If you need it to make changes in your system, I suggest using a process manager instead. For example, you may want to add a unique URL and add it to an event, for use in your application. If it is deemed this is part of your domain (it may not be) you would need to inject a service that could generate a unique URL.
At this point you need to remember that an AR should have little to no dependencies. I suggest defining an interface for this service within the domain. Then define a run time implementation at the client level. A neat trick is to inject the service via the command handler into the handling method on the AR. When you think about it, injecting all dependencies via the constructor ignores their life cycle. This way you can constrain the life cycle of the dependency to when it’s in use.
This is a common problem and I see questions like this crop up all the time. It is often an indicator that something is amiss in your design. As you’ve seen there are many ways to tackle it. You can try a process manager, fatter commands or injected services. I’m sure there are more.
Have you used other approaches? Were they a success? What can we learn from them?