Spring MVC and the HATEOAS constraint
Join the DZone community and get the full member experience.
Join For FreeHATEOAS is a REST architecture principle where hypermedia is used to change application state. To change state, the returned resource representation contains links thereby 'constraining' the client on what steps to take next.
The Spring-HATEOAS project aims to assist those writing Spring MVC code in the creation of such links and the assembly of resources returned to the clients.
The example below will cover a simple scenario showing how links are created and returned for the resource, Bet. Each operation on the resource is described below:
- createBet - this POST operation will create a Bet.
- updateBet - this PUT operation will update the Bet.
- getBet - this GET operation will retrieve a Bet.
- cancelBet - this DELETE operation will cancel the Bet.
@Controller @RequestMapping("/bets") public class BetController { private BetService betService; private BetResourceAssembler betResourceAssembler; public BetController(BetService betService, BetResourceAssembler betResourceAssembler) { this.betService = betService; this.betResourceAssembler = betResourceAssembler; } @RequestMapping(method = RequestMethod.POST) ResponseEntity<BetResource> createBet(@RequestBody Bet body) { Bet bet = betService.createBet(body.getMarketId(), body.getSelectionId(), body.getPrice(), body.getStake(), body.getType()); BetResource resource = betResourceAssembler.toResource(bet); return new ResponseEntity<BetResource>(resource, HttpStatus.CREATED); } @RequestMapping(method = RequestMethod.PUT, value = "/{betId}") ResponseEntity<BetResource> updateBet(@PathVariable Long betId, @RequestBody Bet body) throws BetNotFoundException, BetNotUnmatchedException { Bet bet = betService.updateBet(betId, body); BetResource resource = betResourceAssembler.toResource(bet); return new ResponseEntity<BetResource>(resource, HttpStatus.OK); } @RequestMapping(method = RequestMethod.GET, value = "/{betId}") ResponseEntity<BetResource> getBet(@PathVariable Long betId) throws BetNotFoundException { Bet bet = betService.getBet(betId); BetResource resource = betResourceAssembler.toResource(bet); if (bet.getStatus() == BetStatus.UNMATCHED) { resource.add(linkTo(BetController.class).slash(bet.getId()).withRel("cancel")); } return new ResponseEntity<BetResource>(resource, HttpStatus.OK); } @RequestMapping(method = RequestMethod.GET) ResponseEntity<List<BetResource>> getBets() { List<Bet> betList = betService.getAllBets(); List<BetResource> resourceList = betResourceAssembler.toResources(betList); return new ResponseEntity<List<BetResource>>(resourceList, HttpStatus.OK); } @RequestMapping(method = RequestMethod.DELETE, value = "/{betId}") ResponseEntity<BetResource> cancelBet(@PathVariable Long betId) { Bet bet = betService.cancelBet(betId); BetResource resource = betResourceAssembler.toResource(bet); return new ResponseEntity<BetResource>(resource, HttpStatus.OK); } @ExceptionHandler ResponseEntity handleExceptions(Exception ex) { ResponseEntity responseEntity = null; if (ex instanceof BetNotFoundException) { responseEntity = new ResponseEntity(HttpStatus.NOT_FOUND); } else if (ex instanceof BetNotUnmatchedException) { responseEntity = new ResponseEntity(HttpStatus.CONFLICT); } else { responseEntity = new ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR); } return responseEntity; } }
public class BetResourceAssembler extends ResourceAssemblerSupport<Bet, BetResource> { public BetResourceAssembler() { super(BetController.class, BetResource.class); } public BetResource toResource(Bet bet) { BetResource resource = instantiateResource(bet); resource.bet = bet; resource.add(linkTo(BetController.class).slash(bet.getId()).withSelfRel()); return resource; } }
public class BetResource extends ResourceSupport { public Bet bet; }
resource.add(linkTo(methodOn(BetController.class).cancelBet(betId)) .withRel("cancel"));
{
"links":[
{"rel":"self","href":http://localhost:8080/hateoas-1-SNAPSHOT/bets/0},
{"rel":"cancel","href":http://localhost:8080/hateoas-1-SNAPSHOT/bets/0} ],
"bet":{"id":0,"marketId":1,"selectionId":22,"price":4.0,"stake":2.0,"type":"BACK","status":"UNMATCHED"}
}
The client can therefore use the self link for retrieving and updating the Bet, and also the cancel link to effectively delete it.
This post describes just some of the functionality of the Spring-HATEOAS project which is evolving all the time. For an up to date and more detailed explanation, visit the GitHub pages.
Published at DZone with permission of Geraint Jones, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Trending
-
Building a Robust Data Engineering Pipeline in the Streaming Media Industry: An Insider’s Perspective
-
Mastering Go-Templates in Ansible With Jinja2
-
Performance Comparison — Thread Pool vs. Virtual Threads (Project Loom) In Spring Boot Applications
-
What Is JHipster?
Comments