Spring Pageable sorting is exposing internal naming of dao

Viewed 760

I am building rest microservices build with Spring and Spring Repositories. I want to give the client control of paging and sorting (using spring-data-rest).

public PagedResources<TestDto> find(
    @PageableDefault(page = 0, size = 50) final Pageable pageable,
    final PagedResourcesAssembler<Test> assembler) {

    final Page<Test> pagedResults = testRepository.findAll(pageable);

    return assembler.toResource(pagedResults, testResourceAssembler);
}

above code works great except I am not happy that the sorting is coupled to my dao-layer instead of dto-layer. Example:

http://localhost:8080/my-service/test?size=100&page=0&sort=statusCode,asc

json response (DTO):

{
  "links": [
    ...
    ...
  ],
  "content": [
    {
      "id": 4
      "status": "OK"
      "links": []
    }
  ],
  "page": {
    "size": 100,
    "totalElements": 1,
    "totalPages": 1,
    "number": 0
  }
}

notice the difference between the 'statusCode' sort in request url and the corresponding 'status' in the response dto. From a client persepctive this is not logical. Now I also exposed some internal information of the service. I cannot change the implementation of the service because it can be used by a client and would break the old request after changing the name.

When I add a custom mapping to handle the difference statuscode vs status in the searchrequest:

private PageRequest getPageRequest(final Pageable pageable) {
    final List<Order> newOrder = new ArrayList<>();
    for (final Order order : pageable.getSort()) {
    if (daoToJpaMapping.containsKey(order.getProperty())) {
            newOrder.add(new Order(order.getDirection(), daoToJpaMapping.get(order.getProperty())));
        } else {
            throw new OngeldigSortProperty(order.getProperty() + " is niet toegestaan als sorteerproperty");
        }
    }
    final PageRequest pageRequest = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.by(newOrder));

    return pageRequest;
}

it backfires on me with the hal response bacause it contains the full url based on the jpa names:

{
  "links": [
    {
      "rel": "first",
      "href": "http://localhost:8080/my-service/test?page=0&size=100&sort=statusCode,asc",
...

I could not find a way to solve this issue, except a custom sort,page and size parameter. But building a custom sort attribute that works as great as the spring version does feels like inventing the wheel again. Does it mean I am trying to abuse the spring features? Working with DTO's is fairly common so I was surprised no (spring) tutorial explaining paging in your rest interface is covering this part. Is there a solution?

1 Answers

I had the same issue in my project and solved it by completely separating the Controller and Data layer:

Controller layer objects:

public class QueryOptionsDTO {
  int pageNumber;
  int pageSize;
  String sortField;
  String sortDirection

  public Pageable toPageable() {
    Sort sort = Sort.by(sortField);
    if (sortDirection.equals("asc")) {
      sort = sort.ascending();
    } else {
      sort = sort.descending();
    }
    return PageRequest.of(pageNumber, pageSize, sort);
  }
}
public class ResultPageDTO<YourObjectDTO> {
  List<YourObjectDTO> results;
  int pageNumber;
  int pageSize;
  int totalPages;
  int totalSize;

  public <YourDomainObject> ResultPageDTO<YourObjectDTO> from(Page page, Function<YourDomainObject, YourObjectDTO> mapper) {
    // Custom logic to map to ResultPageDTO<YourObjectDTO>
  }
}
@GetMapping
public ResultPageDTO<YourObjectDTO> find(@RequestParam QueryOptionsDTO queryOptionsDTO) {

    PageResult<YourObject> pagedResults = objectQueryService.find(queryOptionsDTO.toPageable());

    return ResultPageDTO.of(pagedResults);
}

This is pseudo code, just to show you how it could be implemented.

Related