So far I have the following.
There are three different types of service (From DDD by Eric Evans) :
1) Application Services (front end, transport, e.g. unpacking xml files, communicating to infrastructure)
2) Domain Services (Always defined in terms of Domain objects, in SAME LAYER as Entities, i.e. Domain Layer, Perform operations which don’t naturally fit into the domain object, which may use global facilities)
Domain services should have methods named using the UBIQUITOUS LANGUAGE
3) Infrastructure services (communication to other systems, e.g. email, SOAP requests)
Domain services are the tricky ones.
Personally so far I want to always try to put something on the Domain. If it looks really messy and is hard to test, maybe it needs to be in a service.
An alternative approach is to build the service first and then see if it makes sense to move it into the domain. The problem with this second approach is that you end up having to remove the service afterwards, but it might be easier to communicate to people who are used to always having a service, it becomes more obvious once you see the code that it should be moved into the Domain Object.
For example we have been building a domain object which accepts user input from a front end in the form of an email address and the answers to a set of questions, a bit like a survey. The names have been changed to protect the innocent and prevent any legal issues!
One of the main things you can do with a competition is of course, enter it. So, should there be a service which allows you to enter or should you just enter the Competition ?
The first cut involved a service called DomainObjectSubmissionService
with a method submit()
which took the responses the user had made in the form of a map of question ids to answers.
public DomainObjectSubmission submit(String emailAddress, Map<Integer, List<String>> responsesToQuestions, DomainObject domainObject) {
DomainObjectSubmission submission = responseValidator.validate(emailAddress, responsesToQuestions, domainObject);
if(submission.isValid()){
enter(domainObject, emailAddress, responsesToQuestions);
}
return submission;
}
private void enter(DomainObject domainObject, String emailAddress, Map<Integer, List<String>> responsesToQuestions) {
DomainObjectEntry entry = new DomainObjectEntry();
entry.setEmailAddress(emailAddress);
for (DomainObjectQuestion question : domainObject.getQuestions()) {
if (question instanceof CheckBoxQuestion && !responsesToQuestions.containsKey(question.getId())) {
DomainObjectResponse response = new DomainObjectResponse();
response.setResponseValue("unchecked");
entry.putResponse(question, response);
} else if (responsesToQuestions.containsKey(question.getId())) {
DomainObjectResponse response = new DomainObjectResponse();
String submittedResponseValue = toResponseValue(question, responsesToQuestions.get(question.getId()));
response.setResponseValue(submittedResponseValue);
entry.putResponse(question, response);
}
}
entry.setDomainObject(domainObject);
repository.save(entry);
}
So here are two methods, submit
and enter
. This might be an example of application vs domain services… perhaps submit
is an application service and enter
is a domain service. enter
is definitelly a domain service, it is TOTALLY using domain objects and manipulating them. The reason it should move to the domain is that it is doing the classic thing of calling lots to the same object, the entry. This is the indication that it should move, infact it is more specific, in that it is the same instance if there were two instances of the same class then it would be leaning towards a service as exemplified by the old Account Transfer example.
Alternatively, you could argue that constructing the entry is a FACTORY METHOD as it is building an object up. I would argue that the method still belongs on the Domain object but that it could use a factory method inside it to actually do the work. Although the thing about factories is that they are not so useful if only one thing ever uses them.
This is also something ive heard said about services. So, for example in this service we have only one client, the web controller. Should there really be a service ? It is already looking more liek and APPLICATION SERVICE, i.e. its not only dealing with domain objects, it is validating a submission and then calling the domain.
It seems that this logic could easily live in the controller.
Finally, what about this validation object ? Should this live inside the domain object aswell ?
We already made the decision to not represent the user response with a custom domain object, i.e. we just use a simple map. The validator however does do some extra validation which requires a database call to find out if the person has already submitted or not for that email.
Without adding repository access to the domain object (which may not be a bad idea) this kind of has to live in the service.
Is the service an APPLICATION service or a DOMAIN service ?
I think that if we set the email address on the competition and then set the responses and THEN validated the competition, it would be a domain service, because the validator would be also a domain level object.
So a rule of thumb:
If a method is making many calls to the same instance of a class, and is purely manipulating domain objects which either belong to or are associated with that instance, it should move onto the domain object
This is quite a simple rule and is basically ENCAPSULATION
Firstly, this method doesn’t have parameters which are domain objects… should there be a domain object which represents the Response from the user, rather than a Map ? Maybe. just using a Map may be simple but it does not declare any intent.
I intuitively wanted to move this into a method Competition.enter()
.
But is it the right thing to do ?
One thing to think about is that a domain service will usually coordinate between domain objects. A really good example is that of transferring money between two accounts – if you put this on account it would be a bit like it was breaking the encapuslation, it would become not obvious which account was which.
Secondly, if the parameters really aren’t a domain object, maybe they shouldnt be on a service but on the domain object.