Search code examples
javaspringspring-bootspring-hateoashateoas

Representing a request body on HATEOAS link


I have a simple question regarding how do I link an endpoint through HATEOAS if that endpoint requires a valid @RequestBody? I've seen another question regarding this but with no straight answers and so I wonder if the question was not clear enough.

Take a look at the code below:

@GetMapping(path = "/notification/{id}")
@ResponseStatus(HttpStatus.OK)
public NotificationItemResponse getNotification(@PathVariable final String id) {
    return notificationItemMapper.toResponse(findUseCase.findNotification(id))
            .add(linkTo(methodOn(NotificationItemController.class).getNotification(id)).withSelfRel())
            .add(linkTo(methodOn(NotificationItemController.class).saveNotification()).withRel("save")) <- error here, saveNotification expects a valid request body
            .add(linkTo(methodOn(NotificationItemController.class).revokeNotification(id)).withRel("revoke"))
            .add(linkTo(methodOn(NotificationItemController.class).markNotificationAsSeen(id, "{userName}")).withRel("visualize"));
}

saveNotification() is a method on this controller that requires a request body. That request body is a long json containing a notification message, target users, etc.

It doesn't seem right to create a dummy body just to pass down here, and it hardly seem right to pass a null value. What is the correct approach here? How do I correctly link a method that requires a request body? More specifically, what do I pass down as that request body?

What is the best practice here, other than passing a null or dummy body, as I stated before as a non-optimal solution?


Solution

  • The question is pretty old, but I faced the same issue today, and it looks pretty hard to find the correct answer.

    After some research, I found this example in Spring HATEOAS Docs: 3.3 Affordances

    @GetMapping("/employees/{id}")
    public EntityModel<Employee> findOne(@PathVariable Integer id) {
    
      Class<EmployeeController> controllerClass = EmployeeController.class;
    
      // Start the affordance with the "self" link, i.e. this method.
      Link findOneLink = linkTo(methodOn(controllerClass).findOne(id)).withSelfRel(); 
    
      // Return the affordance + a link back to the entire collection resource.
      return EntityModel.of(EMPLOYEES.get(id), //
          findOneLink //
              .andAffordance(afford(methodOn(controllerClass).updateEmployee(null, id))) 
              .andAffordance(afford(methodOn(controllerClass).partiallyUpdateEmployee(null, id)))); 
    }
    

    In this case, they use a method afford(...), which works pretty similar to linkTo(...). Looks like passing a null object is a best practice, or at least it is encouraged by the Spring team. So in your case it would look like this:

    .add(linkTo(methodOn(NotificationItemController.class).saveNotification(null)).withRel("save"))