Search code examples
javamodel-view-controllerdesign-patterns

Layer between controller and business service


Interested in practices for implementing a layer between the controller and the business service.

public class Controller {
    public Response1 test1(Request1 request1){
        ServiceInputDto1 serviceInputDto1 = mapper.map(request1, ServiceInputDto1.class);
        ServiceOutputDto1 serviceOutputDto1 = service1.test(serviceInputDto1);
        Response1 response1 = mapper.map(serviceOutputDto1, Response1.class);
        return response1;
    }

    public Response2 test2(Request2 request2){
        ServiceInputDto2 serviceInputDto2 = mapper.map(request2, ServiceInputDto2.class);
        ServiceOutputDto2 serviceOutputDto2 = service2.test(serviceInputDto2);
        Response2 response2 = mapper.map(serviceOutputDto2, Response2.class);
        return response2;
    }
}

Following the example above, it seems worth separating the mapping and calling business services from the controller. I suppose that it will have a dto mapping, some initial validation of the input data and a call to the business service.

  1. Is it correct in such cases to put it in a separate layer at all?
  2. What design pattern can this layer be considered to be?
  3. Is it worth it to combine all calls to business services into one layer (one class)?

In my practice, there was a facade (as a design pattern), but it seems there is a more precise definition of what I want.


Solution

  • In case if you need to reduce the code duplication

    class Controller {
        private Service1 service1;
        private Service2 service2;
        private InputOutputMapping mapping;
    
        public Response1 test1(Request1 request1){
            return mapping.apply(
                request1,
                ServiceInputDto1.class,
                serviceInputDto1 -> service1.test(serviceInputDto1),
                Response1.class
            );
        }
    
        public Response2 test2(Request2 request2){
            return mapping.apply(
                request2,
                ServiceInputDto2.class,
                serviceInputDto2 -> service2.test(serviceInputDto2),
                Response2.class
            );
        }
    }
    
    class InputOutputMapping {
        private Mapper mapper;
    
        public <REQ, IN_DTO, OUT_DTO, RESP> RESP apply(
            REQ requestObject,
            Class<IN_DTO> inDtoClass,
            Function<IN_DTO, OUT_DTO> serviceFunction,
            Class<RESP> responseClass
        ) {
            final IN_DTO inputDto = mapper.map(requestObject, inDtoClass);
            final OUT_DTO outputDto = serviceFunction.apply(inputDto);
            final RESP response = mapper.map(outputDto, responseClass);
            return response;
        }
    }