5

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

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.fimndUser(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?

  • 1
    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 '19 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 – YouYou Nov 12 '19 at 11:50
  • 1
    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 '19 at 12:01
  • 1
    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 '19 at 12:05
  • 3
    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 '19 at 12:32
14

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.

| improve this answer | |
  • 2
    While your point is very valid, I think it does not answer the question. – tom van green Nov 13 '19 at 14:17
  • 1
    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? – Berin Loritsch Nov 13 '19 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... – tom van green Nov 13 '19 at 17:06
  • 2
    Edited, hopefully this is clearer. – Berin Loritsch Nov 13 '19 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. – Imran Javed Jan 1 at 22:36
1

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.

| improve this answer | |
  • 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 at 19:26
0

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

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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