Domanda

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?

È stato utile?

Soluzione

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.

Altri suggerimenti

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.

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

Autorizzato sotto: CC-BY-SA insieme a attribuzione
scroll top