Skip to content

Limitations of nested embedded resources through private HalRepresentationModel  #1870

Open
@R-o-la-n-d

Description

@R-o-la-n-d

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions