principlecsharpMinor
Moving business logic towards the domain object as a design pattern
Viewed 0 times
thebusinesstowardsdesignlogicmovingobjectdomainpattern
Problem
My project is composed of several parts: Repositories, Services, Domain objects, etc in a .NET MVC web application.
My repositories handle all data i/o, the services are responsible for CRUD operations as well as other logic, such as getting a count of objects from the database.
I am trying to shift from an "anemic" domain model where my domain objects are simply classes with properties to having more business logic where it belongs. In this case, I would like to have the domain object handle the logic where it checks the database for duplicates, but I also don't want to have the code that communicates with the repository inside of the domain object (I read it is bad practice to have an object responsible for persistence with the database, so I'm assuming this is the same thing.)
I don't particularly like this solution, as it just doesn't feel good, and I'd like to get a better handle on how to handle these sorts of situations. Can anybody give me some advice here?
My repositories handle all data i/o, the services are responsible for CRUD operations as well as other logic, such as getting a count of objects from the database.
I am trying to shift from an "anemic" domain model where my domain objects are simply classes with properties to having more business logic where it belongs. In this case, I would like to have the domain object handle the logic where it checks the database for duplicates, but I also don't want to have the code that communicates with the repository inside of the domain object (I read it is bad practice to have an object responsible for persistence with the database, so I'm assuming this is the same thing.)
I don't particularly like this solution, as it just doesn't feel good, and I'd like to get a better handle on how to handle these sorts of situations. Can anybody give me some advice here?
public interface IDuplicateChecker where T : class
{
bool IsDuplicate(T t);
}
public class DuplicateDocumentChecker : IDuplicateChecker
{
public bool IsDuplicate(Document doc)
{
IDocumentService documentService = new DocumentService();
// simplified check -- really there are a few other properties to match
if (documentService.Count(doc.DocumentNumber) > 0)
{
return true;
}
return false;
}
}
public class Document
{
public Document(string documentNumber)
{
// validation and other removed for brevity
DocumentNumber = documentNumber;
}
// simplified document properties for brevity
public string DocumentNumber { get; set; }
public bool IsDuplicate { get; set; }
public void CheckForDuplicates(IDuplicateChecker checker)
{
IsDuplicate = checker.IsDuplicate(this);
}
}Solution
First, I would like to say thank you for asking this question, because this is the type of situation that average enterprise devs face every day and being able to recognize that something doesn't "feel" right even though all the rules are followed is a good indicator that you're on the right track for figuring out what is going to be a good implementation and what isn't.
Getting to the code, from an overall perspective I would recommend the pattern of creating services in front of your domain layer, instead of your domain objects being fed into services that are dependencies of util/helper classes. An app layer can construct a domain Document object and pass it to a DocumentService, but encapsulating a DocumentService behind something that the app layer doesn't interact with could become hard to maintain.
A DocumentService could deal with saving the Document to the database, and as it's saving, it can then check for duplicates. Whether it checks for duplicates inline in the method or it calls another duplicate-checking object/util to do this is up to how big the method gets and how many different things the service is doing in the method.
Looking at your existing code, I'd recommend not getting too dogmatic about "one role, one responsibility--one class!" because it can be argued that
I think when you have a DocumentService inside a very specific class used for a very specific purpose, it's a bit of a smell, because your DocumentService starts seeming like a bit of a Swiss army knife you start including inside all document-related objects, because hey, it's where document-related things go!
After all of that, if you implement according to my advice, your Document may remain a glorified property bag (though I would remove the properties like IsDuplicate from the Document. They don't belong on a domain entity. IsDuplicates should be refactored most simply to a boolean
I would recommend focusing on your domain objects as smart entities first. That will help keep your mental model of them as a business object, but keeping the "smart" aspect in mind will allow you to apply "smart" logic to your objects. For example, if you have a one-to-many relationship between your objects, how does your domain track changes between these objects? If a child object assigns its parent reference, the parent object should have a reference to the child in its collection of children. So you might implement that by writing a "smart" setter so that when an Order assigns its Customer property, the Customer property adds it to its Orders collection if it doesn't exist. I think that's more in line with a domain object that is still an "entity" of sorts. Of course, there's definitely more that comes with the evolution of an application, but I'd start there.
For the time being, I'd focus on having intuitive services sitting in front of your domain, so the application layer can call these services, using domain objects to communicate by constructing them and passing them in to the services or having them returned from the services, or both.
Getting to the code, from an overall perspective I would recommend the pattern of creating services in front of your domain layer, instead of your domain objects being fed into services that are dependencies of util/helper classes. An app layer can construct a domain Document object and pass it to a DocumentService, but encapsulating a DocumentService behind something that the app layer doesn't interact with could become hard to maintain.
A DocumentService could deal with saving the Document to the database, and as it's saving, it can then check for duplicates. Whether it checks for duplicates inline in the method or it calls another duplicate-checking object/util to do this is up to how big the method gets and how many different things the service is doing in the method.
Looking at your existing code, I'd recommend not getting too dogmatic about "one role, one responsibility--one class!" because it can be argued that
documentService.SaveDocument(document) also has one responsibility (having to check for duplicates is superfluous--it's not part of the public contract of this method, although it may require a dependency or two passed in to the constructor). I think when you have a DocumentService inside a very specific class used for a very specific purpose, it's a bit of a smell, because your DocumentService starts seeming like a bit of a Swiss army knife you start including inside all document-related objects, because hey, it's where document-related things go!
After all of that, if you implement according to my advice, your Document may remain a glorified property bag (though I would remove the properties like IsDuplicate from the Document. They don't belong on a domain entity. IsDuplicates should be refactored most simply to a boolean
hasDuplicates or duplicatesExist as a local variable inside a method that has a responsibility to check for duplicates). And I think it's okay if it stays that way for now if your domain is not very fleshed out. It would be worse to put responsibilities/functionality on a domain object that didn't belong, because that's the type of problem that once is in place is really hard to break out because it's in such critical spot.I would recommend focusing on your domain objects as smart entities first. That will help keep your mental model of them as a business object, but keeping the "smart" aspect in mind will allow you to apply "smart" logic to your objects. For example, if you have a one-to-many relationship between your objects, how does your domain track changes between these objects? If a child object assigns its parent reference, the parent object should have a reference to the child in its collection of children. So you might implement that by writing a "smart" setter so that when an Order assigns its Customer property, the Customer property adds it to its Orders collection if it doesn't exist. I think that's more in line with a domain object that is still an "entity" of sorts. Of course, there's definitely more that comes with the evolution of an application, but I'd start there.
For the time being, I'd focus on having intuitive services sitting in front of your domain, so the application layer can call these services, using domain objects to communicate by constructing them and passing them in to the services or having them returned from the services, or both.
Context
StackExchange Code Review Q#77551, answer score: 4
Revisions (0)
No revisions yet.