Following this post https://stackoverflow.com/questions/21554977/should-services-always-return-dtos-or-can-they-also-return-domain-models and best practices in Software Arch suggestions by Martin Fowler https://martinfowler.com/eaaCatalog/serviceLayer.html#:~:text=A%20Service%20Layer%20defines%20an,the%20implementation%20of%20its%20operations
A Service Layer defines an application's boundary [Cockburn PloP] and its set of available operations from the perspective of interfacing client layers. It encapsulates the application's business logic
I have a problem doing so consider the following:
UserService {
UserDto findUser();
}
UserService should be fine if used in the controller where I just need data only so dto is sufficient,
But here is the problem if I used this service in another service say e.g CustomerService
I need the model itself the User
object since the model should be managed by some persistence context
e.g
CustomerService {
void addCustomer() {
Customer customer = new Customer();
User user = userService.findUser(xxx); // BAM compilation fails since findUser returns UserDto not User
customer.setUser(user);
}
}
What would be best practice here? Should I create 2 copies of findUser
method with 2 different return types or 2 copies of UserService
class one for controller use and other for service or core package use? Or should I consider the proxy pattern?
-
5The same Martin Fowler says (If I do recall well), that using DTOs between layers is overkill and make things too complicated. Why don't you simply use domain models and delegate the mapping to DTOs to the controller? Basically, is the only component that really uses DTOs as response model,Laiv– Laiv11/12/2019 11:29:14Commented Nov 12, 2019 at 11:29
-
1IMO, Mapping itself is considered as a business logic code, Plus this would expose the model to the controller layer some would not see any problem with that since it won't be exposed to the consumer, But IMO this contradicts with the idea of ModularityYouans– Youans11/12/2019 11:50:46Commented Nov 12, 2019 at 11:50
-
5Exposing the model to the controller is ok. The dependency keeps flowing from volatile code to a more stable one. The problem would be exposing the domain to these DTOs. Controllers are drivers and it's expected from drivers to transform inputs and outputs so that both elements (callee and caller) don't need to know about each other models or domains. It's not a service goal to generate DTOs for every possible consumer. That's the controller's concern.Laiv– Laiv11/12/2019 12:01:41Commented Nov 12, 2019 at 12:01
-
2The opposite leads you to your current situation. Having to offer different response models for different consumers from code that is meant to be agnostic to these issues.Laiv– Laiv11/12/2019 12:05:31Commented Nov 12, 2019 at 12:05
-
4I would consider the mistake here (a common one) to be having services depend on services - the service should just be an externally-facing abstraction from the controller and internally it should simply be a means of orchestration - fetch the data, populate and invoke the (pure) domain, return (or persist) the output. The service itself is NOT the business logic component. Its only injected dependencies should be interfaces for external dependencies like databases.Ant P– Ant P11/12/2019 12:32:03Commented Nov 12, 2019 at 12:32
4 Answers 4
A really simple rule of thumb is that you should only use a DTO when you need to ... transfer data. That means use a DTO at the boundaries of your web API, or when you are sending the object on a message bus. Internally, just use your domain objects.
The only reason a DTO should exist is the limitations of the (de)serialization layers. Those need simple objects without complex logic.
Recommendations:
- Use standard models in your services;
- Convert the model to a DTO in the controller (i.e. in your web application) just prior to serialization;
- If you use asynchronous messaging, convert the model to a DTO just before pushing the DTO on the message queue.
This allows you to use your services as you desire, and save the DTOs for when they are actually required.
-
3While your point is very valid, I think it does not answer the question.tom van green– tom van green11/13/2019 14:17:09Commented Nov 13, 2019 at 14:17
-
4The best practice is to not use DTOs in your service layer. Only use them at the edges. Without DTOs in the service layer, there is no conflict for what the OP wants. Or am I missing something?Berin Loritsch– Berin Loritsch11/13/2019 15:23:19Commented Nov 13, 2019 at 15:23
-
1Ah, I think I get your point now ;) Do I understand you correctly that you are saying the service should not return a dto and that it should be the controllers responsibility to convert it to a dto? If so you could maybe update your answer to make this more clear, as I completely missed this point and thought you are discouraging OP to use dtos at all. Edit: Hmm, I can't change the downvote until you edit your answer...tom van green– tom van green11/13/2019 17:06:29Commented Nov 13, 2019 at 17:06
-
1If you want to keep services/business logic independent of consumer, then it is always better to have at api level. Idea is to keep dto convertion at edge.Imran Javed– Imran Javed01/01/2020 22:36:15Commented Jan 1, 2020 at 22:36
-
2Yes, a DB entity, if that is what your system is designed around. The only place you need to use a POJO is when you need to serialize and deserialize data through Web API.Berin Loritsch– Berin Loritsch08/31/2021 16:37:49Commented Aug 31, 2021 at 16:37
IMO the accepted answer promotes something which you should not be doing in the service layer, ie returning your domain objects. Instead, the service layer should encapsulate your domain model and return DTOs via its interface. The mapping should happen inside that layer itself.
So coming back to the problem which the OP's having, he's trying to communicate with another service to get some data filled into his domain object. The best way to solve this is to fetch that data straight inside the CustomerService (probably via an injected UserRespository). In fact, this is what services supposed to do. They should orchestrate and manipulate your domain objects, and for that, you may require talking to multiple repositories. Just because it's called the CustomerService, it doesn't have to be 'only' talking to a CustomerRepository. Repositories will map one-to-one with your database tables but your services should map to your domain. Don't mix up concepts.
-
2This answer feels halfway between a comment and an answer. You. could make. it a. better answer by. focusing on and spelling out your answer to OP's question, then talking about why the approach described in the accepted answer is something one "should not be doing". I could +1 an answer like that.joshp– joshp08/30/2020 19:26:52Commented Aug 30, 2020 at 19:26
-
1I was agreeing with this answer until I saw that a service could inject other repositories. If the service layer has business logic in it, it would be great to bypass that and go to the repository directly.Cristian– Cristian12/22/2020 19:13:56Commented Dec 22, 2020 at 19:13
-
@Cristian I didn't fully understand your comment. If you were disagreeing with the fact that a service could talk to multiple repos, I would like to know why. In my defense, the example discussed here might not have the best terminology. The service could be a 'MemberService' where you deal with customers who actually have registered with you already (members).Ε Г И І И О– Ε Г И І И О12/22/2020 20:00:34Commented Dec 22, 2020 at 20:00
-
Not every service is at the same level. For example it is not uncommon for a microservice to have other "services" that are not exposed to the outside world. I would agree in fact that the microservice should handle the translation to DTOs. But I don't agree that you need to add that complexity if you are not transferring objects.Berin Loritsch– Berin Loritsch03/28/2022 11:26:56Commented Mar 28, 2022 at 11:26
-
" Instead, the service layer should encapsulate your domain model and return DTOs via its interface" What is the benefit of this?Mehdi Charife– Mehdi Charife04/16/2024 23:53:28Commented Apr 16, 2024 at 23:53
I think that there is a design misconception at the core of this question: an application service that depends on other application service.
When you start doing that you are just abandoning DDD way, your application services are not application services anymore but eventually they transform in transaction scripts. Your domain objects will loose behaviours because you start reusing application logic and what you need will be just "model class" and distinguishing them from DTO doesn't worth. Dependencies between application services are definetly an anaemic domain smell
If you need to invoke some domain logic that makes decision (or fetches data) with data outside your aggregate, you could use domain services, that are already part of the domain layer so they can accept or retrieve domain objects, i wrote about them here if you are interested (link)
Regarding DTO, they are widely adopted as a separation boundary from the inner application state and the presentation data. You need them when your application is complex and what you see from the outside is different from what there is inside. Bonus: they won't make domain logic leak.
The only different thing that you can do is to abstract away the DTO
public UserModel findUser(String userId) {
// ...
return new UserModel(repository.findById());
}
and the controller
UserDTO dto = service.findUser(userId).asDTO();
and UserModel
public class UserModel implements Model<User , UserDTO> {
@Override
public UserDTO toDTO (User entity) {
//...
}
}
But this just moves away entity > DTO and DTO > entity mapping logic from application logic
One suggestion to the problem was to inject repositories of models from other services and it is probably the worst thing that you can do. Whole purpose of having a service layer is encapsulate your domain logic within so that other services could use you without knowing about your business rules. DTO as the name suggests should be used to transfer objects through network. When one service layer object is calling another within the same application service, there is not any transfer so it has no use at all. Another use of DTOs is to have different interfaces to your application. You might be returning an apple model from your service but the properties of the apple could change when you return from a rest endpoint or publish it as an event or when you use gRPC. Your service should return the model and your controllers should convert the model to whatever their protocol is expecting.
Explore related questions
See similar questions with these tags.