Search code examples
dtosoftware-designservice-layer

Should service layer accept a DTO or a custom request object from the controller?


My understanding is that the service layer should always return a DTO so that domain (entity) objects are preserved within the service layer. But what should be the input for the service layer from the controllers?

I outlined three of my own suggestions below:

Method 1: In this method the domain object (Item) is preserved within the service layer.

class Controller
{
    @Autowired
    private ItemService service;

    public ItemDTO createItem(IntemDTO dto)
    {
        // service layer returns a DTO object and accepts a DTO object
        return service.createItem(dto);
    }
}

Method 2: This is where the service layer receives a custom request object. I have seen this pattern extensively in AWS Java SDK and also Google Cloud Java API

class Controller
{
    @Autowired
    private ItemService service;

    public ItemDTO createItem(CreateItemRequest request)
    {
        // service layer returns a DTO object and accepts a custom request object
        return service.createItem(request);
    }
}

Method 3: Service layer accepts a DTO and returns a domain object. I am not a fan of this method. But its been used extensively used at my workplace.

class Controller
{
    @Autowired
    private ItemService service;

    public ItemDTO createItem(CreateItemRequest request)
    {
        // service layer returns a DTO object and accepts a DTO object
        Item item = service.createItem(request);
        return ItemDTO.fromEntity(item);
    }
}

Which one of these methods result in code that is more maintainable?


Solution

  • I'm from C# background but the concept remains same here.

    In a situation like this, where we have to pass the parameters/state from application layer to service layer and, then return result from service layer, I would tend to follow separation-of-concerns. The service layer does not need to know about the Request parameter of you application layer/ controller. Similarly, what you return from service layer should not be coupled with what you return from your controller. These are different layers, different requirements, separate concerns. We should avoid tight coupling.

    For the above example, I would do something like this:

    class Controller
    {
         @Autowired
         private ItemService service;
    
         public ItemResponse createItem(CreateItemRequest request)
         {
            var creatItemDto = GetDTo(request);
            var itemDto = service.createItem(createItemDto);
            return GetItemResponse(itemDto);
        }
    }
    

    This may feel like more work since now you need to write addional code to convert the different objects. However, this gives you a great flexiblity and makes the code much easier to maintain. For example: CreateItemDto may have additional/ computational fields as compared to CreateItemRequest. In such cases, you do not need to expose those fields in your Request object. You only expose your Data Contract to the client and nothing more. Similarly, you only return the relevant fields to the client as against what you return from service layer.

    If you want to avoid manual mapping between Dto and Request objects C# has libaries like AutoMapper. In java world, I'm sure there should be an equivalent. May be ModelMapper can help.