I have found interesting problem in DDD, which can be solved extremely simply without DDD, but with DDD looks huge.
Let's say I have: Category - AR (AggregateRoot) Product - AR
Category can have multiple products and also a featured product, like this:
public class Category : AggregateRoot { public List<Product> Products { get; set; } public Product FeaturedProduct { get; set; } Product have Name, Description and IsActive flag:
public class Product : AggregateRoot { public string Name { get; set; } public string Description { get; set; } public bool IsActive { get; set; } } Now the business rules are:
- Can not Set Inactive product as featured.
- Can not Deactivate product when set as featured.
Proposed solutions (none of them is proper from DDD perspective):
Option 1) Put this business rules to Command Handlers, but it my opinion this would be code-smell. I believe such business rules should be part of Domain (not application layer, and command handlers are application layer).
Option 2) Create CategoriesProductsDomainService and put logic that touches these 2 AR there, e.g.:
2A: public void DeactivateProduct(int productId) { // load product from products_repository // call categories_repository to check if any category has this product as featured // deactivate product } or
2B: public void DeactivateProduct(Product product, List<Category> categories_with_product_as_featured) { if (categories_with_product_as_featured.Any(x => x.FeaturedProduct.Id == product.Id)) { // return or throw validation error } product.Deactivate(); } but such approach removes business logic from Aggregate and place it in additional DomainService. Not sure if this is proper approach.
Option 3) When Deactivating product, dispatch a ProductDeactivatedDomainEvent and handle it in same transaction, and event handler can throw an exception if the business rules is not met.
This approach however have 3 drawbacks:
- I don't think DomainEvent should be used to validate transaction that just happened
- throwing exception for something that is just validation error is a design-smell
- it is not unit testable without mocks (DomainEventsDispatcher is part of infrastructure)
Final thoughts
Looks like Option 2) is the most DDD friendly so far. Nevertheless I see a danger here, that a lot of logic will be removed from Aggregates with new business rules.