22

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?

6
  • 3
    The 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
    Nov 12, 2019 at 11:29
  • 1
    IMO, 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 Modularity
    – Youans
    Nov 12, 2019 at 11:50
  • 3
    Exposing 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
    Nov 12, 2019 at 12:01
  • 2
    The 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
    Nov 12, 2019 at 12:05
  • 4
    I 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
    Nov 12, 2019 at 12:32

4 Answers 4

34

A really simple rule of thumb is that you should only use a DTO object when you need to ... transfer data. That means use a DTO at the boundaries in 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.

Recommendation:

  • 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.

15
  • 3
    While your point is very valid, I think it does not answer the question. Nov 13, 2019 at 14:17
  • 3
    The 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? Nov 13, 2019 at 15:23
  • 1
    Ah, 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... Nov 13, 2019 at 17:06
  • 2
    Edited, hopefully this is clearer. Nov 13, 2019 at 19:42
  • 1
    If 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. Jan 1, 2020 at 22:36
15

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.

4
  • This 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
    Aug 30, 2020 at 19:26
  • I 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
    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). 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. Mar 28, 2022 at 11:26
3

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

0

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.