I have been adapting domain-driven design for about 8 years now and even after all these years, there is still one thing, that has been bugging me. That is checking for a unique record in data storage against a domain object.
In September 2013 Martin Fowler mentioned the TellDon'tAsk principle, which, if possible, should be applied to all domain objects, which should then return a message, how the operation went (in object-oriented design this is mostly done through exceptions, when the operation was unsuccessful).
My projects are usually divided into many parts, where two of them are Domain (containing business rules and nothing else, the domain is completely persistence-ignorant) and Services. Services knowing about repository layer used to CRUD data.
Because uniqueness of an attribute belonging to an object is a domain/business rule, it should be long to domain module, so the rule is exactly where it is supposed to be.
In order to be able to check the uniqueness of a record, you need to query current dataset, usually a database, to find out, whether another record with a let's say Name
already exists.
Considering domain layer is persistence ignorant and has no idea how to retrieve the data but only how to do operations on them, it cannot really touch the repositories itself.
The design I have been then adapting looks like this:
class ProductRepository
{
// throws Repository.RecordNotFoundException
public Product GetBySKU(string sku);
}
class ProductCrudService
{
private ProductRepository pr;
public ProductCrudService(ProductRepository repository)
{
pr = repository;
}
public void SaveProduct(Domain.Product product)
{
try {
pr.GetBySKU(product.SKU);
throw Service.ProductWithSKUAlreadyExistsException("msg");
} catch (Repository.RecordNotFoundException e) {
// suppress/log exception
}
pr.MarkFresh(product);
pr.ProcessChanges();
}
}
This leads to having services defining domain rules rather than the domain layer itself and you having the rules scattered across multiple sections of your code.
I mentioned the TellDon'tAsk principle, because as you can clearly see, the service offers an action (it either saves the Product
or throws an exception), but inside the method you are operation on objects through using procedural approach.
The obvious solution is to create a Domain.ProductCollection
class with an Add(Domain.Product)
method throwing the ProductWithSKUAlreadyExistsException
, but it's lacking in performance a lot, because you would need to obtain all the Products from data storage in order to find out in code, whether a Product already has the same SKU as the Product you are trying to add.
How do you guys solve this specific issue? This is not really a problem per se, I have had service layer represent certain domain rules for years. The service layer usually also serves more complex domain operations, I am simply wondering whether you have stumbled upon a better, more centralized, solution during your career.