spring-projects / spring-projects/spring-hateoas

Limitations of nested embedded resources through private HalRepresentationModel

Open
#1,870 0 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Java
Stars
1.1k
Forks
476
PR merge metrics
No merged PRs in 30d

Description

We have a REST service which is implement with SpringBoot and HATEOAS. The following method exists within the WebService

@Override
public ResponseEntity<?> getCar(String carId) {
        Car car = carService.findById(carId).orElseThrow(() -> {
                throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Car not found!");
        });
               
       // toDTO & HATEOAS Links
       CarItem carItem = carModelAssembler.toModel(car);
         
       HalModelBuilder builder = HalModelBuilder.halModelOf(carItem);
       builder.embed(garageModelAssembler.toModel(car.getGarage()), LinkRelation.of("garage"));
            
       return ResponseEntity.ok(builder.build());
}
 

The response looks like this (abbreviated):

{
   "id":"xyu",
   "garageId":"096a07ea-3970-4968-9cd9-4756ae6a9cb1",
   "_embedded": {
       "garage":{
         "id":"abc",
         "location":"XY",
         "_links":{
            "self":{
               "href":"https://localhost:8443/api/v1/garages/096a07ea-3970-4968-9cd9-4756ae6a9cb1"
            },
        }
     }
  },
  "_links": {
    "self": {
      "href":"https://localhost:8443/api/v1/cars/d589852f-0b3f-4025-86cb-2fa3b35a1339"
    }
  }
}

We would also like to output the embedded objects in our paged responses. We came up with the following as a solution:

@Component
public class CarModelAssembler implements RepresentationModelAssembler<Car, RepresentationModel<CarItem>> {
        private GarageModelAssembler garageModelAssembler;
        private CarMapper carMapper;
 
        public BuildingModelAssembler(CarMapper carMapper, GarageModelAssembler garageModelAssembler) {
                this.carMapper = carMapper;
                this.garageModelAssembler = garageModelAssembler;
        }
 
        @Override
        public RepresentationModel<CarItem> toModel(Car car) {
                return toModel(car, true);
        }
       
        public RepresentationModel<CarItem> toModel(Car car, boolean embedded) {
                CarItem carItem = locationMapper.CarToCarItem(car);
                HalModelBuilder builder = HalModelBuilder.halModelOf(carItem);
               
                // add Links
                builder.link(linkTo(methodOn(CarController.class).getBuilding(carItem.getId())).withSelfRel());
                builder.link(linkTo(methodOn(GarageController.class).getGarage(carItem.getGarage.getId())).withRel("garage"));
               
                if(embedded) {
                        builder.embed(garageModelAssembler.toModel(car.getGarage(), false));
                }
               
                return builder.<CarItem>build();
        }
}

The call in the controller is then as follows:
CollectionModel carItems = pagedResourcesAssembler.toModel(pCars, carModelAssembler);

The whole thing works in principle, but I don't like the fact that:
a. - I have to pack another RepresentationModel (RepresentationModel) around my CarItem (extends RepresentationModel).
b. - I still have to install the embedded switch in the assembler so that there are no infinite nestings
c. - I have to inject the required assemblers for the embedded objects everywhere, which means that sooner or later I have to work with @Lazy

Unfortunately, I cannot use the HalRepresentationModel directly because it is private

I'm hoping someone here has a better idea.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the shown getCar method, HalModelBuilder, RepresentationModelAssembler, and PagedResourcesAssembler usage, then inspect how the private HalRepresentationModel handles nested embedded resources. Reproduce the paged Car response and determine what public API could support nested resources without wrapper models, recursive nesting, or assembler cycles; done means the requested representation works for both single and paged responses.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, spring, spring-boot
Domain
api, backend
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.