Suppose I have a domain service which implements the following business rule / policy:
If the total price of all products in category 'family' exceeds 1 million, reduce the price by 50% of the family products which are older than one year.
Using collection-based repositories
I can simply create a domain service which loads all products in the 'family' category using the specification pattern, then check the condition, and if true, reduce the prices. Since the products are automatically tracked by the collection-based repository, the domain service is not required to issue any explicit infrastructure calls at all – as should be.
Using persistence-based repositories
I'm out of luck. I might get away with using the repository and the specification to load the products inside my domain service (as before), but eventually, I need to issue Save
calls which don't belong into the domain layer.
I could load the products in the application layer, then pass them to the domain service, and finally save them again in the application layer, like so:
// Somewhere in the application layer:
public void ApplyProductPriceReductionPolicy()
{
// make sure everything is in one transaction
using (var uow = this.unitOfWorkProvider.Provide())
{
// fetching
var spec = new FamilyProductsSpecification();
var familyProducts = this.productRepository.findBySpecification(spec);
// business logic (domain service call)
this.familyPriceReductionPolicy.Apply(familyProducts);
// persisting
foreach (var familyProduct in familyProducts)
{
this.productRepository.Save(familyProduct);
}
uow.Complete();
}
}
However, I see the following issues with this code:
- Loading the correct products is now part of the application layer, so in case I need to apply the same policy again in some other use case, I need to repeat myself.
- The cohesion between the specification (
FamilyProductsSpecification
) and the policy is lost, essentially allowing someone to pass the wrong products into the domain service. Note that filtering the products (in-memory) again in the domain service does not help either, as the caller might have passed only a subset of all products. - The application layer has no clue which products have changed, and therefore is forced to save all of them, which might be a lot of redundant work.
Question: Is there a better strategy to deal with this situation?
I thought about something complicated like adapting the persistence-based repository such that it appears as a collection-based one to the domain service, internally keeping track of the products which were loaded by the domain service in order to save them again when the domain service returns.
familyProducts
). I don't quite get your last paragraph though. Do you suggest that having no branching in the business logic legitimizes putting it directly in the app layer? Isn't "calling something in the right order" also an important part of the business logic? – Howler