I've scoured SO and other forums/mailing lists in the search for answers on this topic, and while I know that the answer largely depends on the domain itself, and what is acceptable from an eventual consistency point of view, I still struggle to find a good solution.
The issue has to do with where to validate the business rules proper to the domain.
My domain is an online market place. A member (with the role Seller) can post an Ad to sell an item. The Seller can specify a minimum and a maximum number of items that can be purchased in a single order, and the price of the item.
A Buyer can buy an item off of a specific ad. The following rules have to be observed:
- They can specify the number of items that they would like to buy, which has to be between min and max allowed by the ad.
- They need to be active (as members can be banned).
- The ad needs to be active (Ads can be suspended).
My Market BC is the one that deals with ads and buy transactions. I designed it in the following way:
- Ad Aggregate root
- Member AR
- BuyTransaction AR
I'm struggling with how and where to validate the above business rules, which in this case span multiple aggregates. I would Ideally have a method:
$buyer->buy($adId, $quantity);
That would be called by a BuyItems command
$buyCommand = new BuyItems($adId, $qty);
On the Member aggregate.
Of the options I gather that I have:
Validate outside of the domain, in an outer layer - this means that I would validate the command before sending it into the domain. This would imply some logic leaking outside of the domain, but I would fetch the ad from a read model, validate the constraint (between min and max, ad active, user active), and then send the command. I would also do domain side validation in that case, in the form of a process manager that would issue a compensating action, or at least warn if an inconsistency occurs.
Define a service interface in the domain, and implement a service that gets the data from the read model, then validate in the command handler by calling the service. If data is invalid, then throw an exception. Domain validation would have to occur here as well, because the read model might not be consistent (again using a process manager).
Load up the Ad and Member aggregate roots in the BuyItem handler, and pass it to the $buyer->buy($ad, $member, $qty); then in the buy() method in the AR, check that qty is between min and max. Don't really feel comfortable with this option, as I feed that I'm trying to cram transactional consistency, when I don't really need it (while I need to minimize the risks of commands with out of bound qty, or inactive member, it's not a huge deal if it happens and I issue a corrective action afterwards so I'm perfectly ok with eventual consistency).
Can anyone point me to what the best option is given that scenario?