Spring HATEOAS – Hypermedia APIs with Spring

Spring HATEOAS – Hypermedia APIs with Spring

Slides about how to build hypermedia based APIs with Spring HATEOAS. Presented at SpringOne 2019 in Austin, TX.

977c74bb044a9d4fa90b305824eda390?s=128

Oliver Drotbohm

October 09, 2019
Tweet

Transcript

  1. Spring HATEOAS – Hypermedia APIs with Spring Oliver Drotbohm, Greg

    Turnquist
 
 October 7–10, 2019 Austin Convention Center
  2. None
  3. Unless otherwise indicated, these slides are © 2013-2019 Pivotal Software,

    Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Spring HATEOAS helps building
 hypermedia-based, REST web services with Spring MVC.
  4. Unless otherwise indicated, these slides are © 2013-2019 Pivotal Software,

    Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Spring HATEOAS helps building
 hypermedia-based, REST web services with Spring MVC. What does that even mean? !
  5. Unless otherwise indicated, these slides are © 2013-2019 Pivotal Software,

    Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ What is Hypermedia?
  6. Unless otherwise indicated, these slides are © 2013-2019 Pivotal Software,

    Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ { "createdAt" : "…", "lineItems" : [ … ], "total" : …, … } 5 Application data
  7. Unless otherwise indicated, these slides are © 2013-2019 Pivotal Software,

    Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ { "_links" : { "self" : { "href" : "…", }, "customer" : { "href" : "…" }, … }, "createdAt" : "…", "lineItems" : [ … ], "total" : …, … "_embedded" : { "customer" : { … } } } 6 Hypermedia Elements Application data
  8. Unless otherwise indicated, these slides are © 2013-2019 Pivotal Software,

    Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Hypermedia Elements Static Related resources Static IANA link relations (self)
  9. Unless otherwise indicated, these slides are © 2013-2019 Pivotal Software,

    Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ { "_links" : { "self" : { "href" : "…", }, "customer" : { "href" : "…" }, … }, "createdAt" : "…", "lineItems" : [ … ], "total" : …, … "_embedded" : { "customer" : { … } } } "self" : { "href" : "…", }, "customer" : { "href" : "…" }, 10 Hypermedia Elements Application data IANA link relations Static relationships
  10. Unless otherwise indicated, these slides are © 2013-2019 Pivotal Software,

    Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Hypermedia Elements Static Dynamic Related resources Static IANA link relations (self) Links representing the resource's state
  11. Unless otherwise indicated, these slides are © 2013-2019 Pivotal Software,

    Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ { "_links" : { "self" : { "href" : "…", }, "payment" : { "href" : "…" }, … }, "createdAt" : "…", "lineItems" : [ … ], "total" : …, … "_embedded" : { "customer" : { … } } } "self" : { "href" : "…", }, "payment" : { "href" : "…" }, 13 Hypermedia As The Engine Of Application State Appears only if the
 order can be paid!
  12. Unless otherwise indicated, these slides are © 2013-2019 Pivotal Software,

    Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Why Hypermedia?
  13. Unless otherwise indicated, these slides are © 2013-2019 Pivotal Software,

    Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ RESTBUCKS payment expected preparing cancelled ready completed 1 2 3 4 5 6 From: REST In Practice – O'Reilly Sample code @ GitHub: Spring RESTBucks
  14. Unless otherwise indicated, these slides are © 2013-2019 Pivotal Software,

    Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ RESTBUCKS payment expected preparing cancelled ready completed 1 2 3 4 5 6 7
  15. Unless otherwise indicated, these slides are © 2013-2019 Pivotal Software,

    Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Order cancellation policy Demo
  16. Unless otherwise indicated, these slides are © 2013-2019 Pivotal Software,

    Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Spring HATEOAS
  17. Unless otherwise indicated, these slides are © 2013-2019 Pivotal Software,

    Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Representation Models Links & Affordances Media types Client support
  18. Unless otherwise indicated, these slides are © 2013-2019 Pivotal Software,

    Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Representation Models General support ResourceSupport Item resource
 e.g. /customer/{id} Resources<T> Resource<T> Collection resource
 e.g. /customers 0.x
  19. Unless otherwise indicated, these slides are © 2013-2019 Pivotal Software,

    Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ General support RepresentationModel Item resource
 e.g. /customer/{id} CollectionRepresentationModel<T> EntityRepresentationModel<T> Collection resource
 e.g. /customers Representation Models 1.0
  20. Unless otherwise indicated, these slides are © 2013-2019 Pivotal Software,

    Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Links & Affordances var link = new Link("/something");
 
 assertThat(link.getHref()).isEqualTo("/something");
 assertThat(link.getRel()).isEqualTo(IanaLinkRelations.SELF);
 link = new Link("/something", "my-rel");
 
 assertThat(link.getHref()).isEqualTo("/something");
 assertThat(link.getRel()).isEqualTo(LinkRelation.of("my-rel")); Plain link with
 self relation Dedicated
 relation
  21. Unless otherwise indicated, these slides are © 2013-2019 Pivotal Software,

    Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Links & Affordances var link = new Link("/{segment}/something{?parameter}");
 
 assertThat(link.isTemplated()).isTrue(); 
 assertThat(link.getVariableNames()).contains("segment", "parameter");
 var values = Map.of("segment", "path",
 "parameter", 42); 
 assertThat(link.expand(values).getHref()) 
 .isEqualTo("/path/something?parameter=42"); Template
 variables Expand the template
 using the given values
  22. Unless otherwise indicated, these slides are © 2013-2019 Pivotal Software,

    Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Links & Affordances @Controller class PersonController { @GetMapping("/people") HttpEntity<PersonModel> showAll() { … } @GetMapping("/people/{person}") HttpEntity<PersonModel> show(@PathVariable Long person) { … } }
  23. Unless otherwise indicated, these slides are © 2013-2019 Pivotal Software,

    Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Links & Affordances import static org.sfw.hateoas.server.mvc.WebMvcLinkBuilder.*; var link = linkTo(methodOn(PersonController.class).show(2L))
 .withRel("people"); 
 assertThat(link.getRel()).isEqualTo(LinkRelation.of("people")); assertThat(link.getHref()).endsWith("/people/2"); Type-safe reference to the controller method
  24. Unless otherwise indicated, these slides are © 2013-2019 Pivotal Software,

    Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Links & Affordances @Value
 class PaymentForm {
 CreditCardNumber number;
 }
 class PaymentController {
 
 @PutMapping("/order/{id}/payment")
 ResponseEntity<?> submitPayment(@PathVariable("id") Order order,
 @RequestBody PaymentForm form) { … }
 } Lombok's @Value to generate constructor Inbound payload is a PaymentForm
  25. Unless otherwise indicated, these slides are © 2013-2019 Pivotal Software,

    Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Links & Affordances @Controller class PaymentMetadataController { @GetMapping(path = "/docs/payment", …) ResponseEntity<RepresentationModel<? >> getPaymentMetadata() { var selfLink = ….andAffordance( afford(methodOn(PaymentController.class).submitPayment(…))); return ResponseEntity.ok(new RepresentationModel <>(selfLink)); } } Adds affordance metadata to the link by inspecting the method
  26. Unless otherwise indicated, these slides are © 2013-2019 Pivotal Software,

    Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Links & Affordances var methodInvocation = methodOn(EmployeeController.class).all();
 var link = Affordances.of(linkTo(methodInvocation).withSelfRel()) 
 .afford(HttpMethod.POST)
 .withInputAndOutput(EmployeeDTO.class)
 .withName("create")
 .andAfford(HttpMethod.GET) 
 .withOutput(EmployeeDTO.class)
 .addParameters(
 QueryParameter.optional("name"),
 QueryParameter.optional("role"))
 .withName("search").toLink(); POST to the resource to
 create a new Employee Access all Employees
 and potentially filter
  27. Unless otherwise indicated, these slides are © 2013-2019 Pivotal Software,

    Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ asdasd Media types Representation model Jackson customizations JSON Output +
  28. Unless otherwise indicated, these slides are © 2013-2019 Pivotal Software,

    Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Affordance model factory Jackson customizations asdasd Media types JSON Output +
  29. Unless otherwise indicated, these slides are © 2013-2019 Pivotal Software,

    Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ HAL-FORMS An example
  30. Unless otherwise indicated, these slides are © 2013-2019 Pivotal Software,

    Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Links & Affordances var model = new EntityResourceModel <>(order);
 
 
 
 model.addLink(linkTo(…).withRel("restbucks:payment")); Create representation model Add links pointing to controller methods
  31. Unless otherwise indicated, these slides are © 2013-2019 Pivotal Software,

    Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ HAL-FORMS { "_links" : { "restbucks:payment" : { … }, } } What are we supposed
 to do with this?
  32. Unless otherwise indicated, these slides are © 2013-2019 Pivotal Software,

    Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Links & Affordances public @Bean CurieProvider curieProvider() { return new DefaultCurieProvider("restbucks", UriTemplate.of("/docs/{rel}")); } The link namespace Where to find docs
 describing semantics
  33. Unless otherwise indicated, these slides are © 2013-2019 Pivotal Software,

    Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ HAL-FORMS { "_links" : { "restbucks:payment" : { … }, "curies" : [{ "name" : "restbucks", "href" : "http: //api.acme.com/docs/{rel}", "templated" : true }] } } "Find metadata about the payment resource at
 http://…/docs/payment!" What are we supposed
 to do with this?
  34. Unless otherwise indicated, these slides are © 2013-2019 Pivotal Software,

    Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ HAL-FORMS { … "_templates" : { "default" : { "method" : "put", "properties" : [{ "name" : "number" }] } } } Use an HTTP PUT request. Provide a field named „number“.
  35. Unless otherwise indicated, these slides are © 2013-2019 Pivotal Software,

    Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ HAL-FORMS @Value class PaymentForm { CreditCardNumber number; } class PaymentController { @PutMapping("/order/{id}/payment") ResponseEntity<?> submitPayment(@PathVariable("id") Order order,
 @RequestBody PaymentForm form) { … } } Source for form details PUT derived from this
  36. Unless otherwise indicated, these slides are © 2013-2019 Pivotal Software,

    Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ HAL-FORMS @Value class PaymentForm { CreditCardNumber number; } We need to expose the pattern
  37. Unless otherwise indicated, these slides are © 2013-2019 Pivotal Software,

    Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ HAL-FORMS @Value class PaymentForm { CreditCardNumber number; } @Bean HalFormsConfiguration halFormsConfiguration() { HalFormsConfiguration configuration = new HalFormsConfiguration(); return configuration.registerPattern(CreditCardNumber.class, CreditCardNumber.REGEX); } Expose the pattern to the 
 Jackson customizations
  38. Unless otherwise indicated, these slides are © 2013-2019 Pivotal Software,

    Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ HAL-FORMS { … "_templates" : { "default" : { "method" : "put", "properties" : [{ "name" : "number", "regex" : "[0-9]{16}" }] } } } Use an HTTP PUT request. Provide a field named „number“
 matching the given pattern.
  39. Unless otherwise indicated, these slides are © 2013-2019 Pivotal Software,

    Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ HAL-FORMS Demo
  40. Unless otherwise indicated, these slides are © 2013-2019 Pivotal Software,

    Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Hypermedia Elements Inline resource Separate resource No additional requests Metadata repeatedly transmitted Cachability Additional requests
  41. Unless otherwise indicated, these slides are © 2013-2019 Pivotal Software,

    Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Media types JSON-LD, JSON-API Contributions welcome! HAL
 http://bit.ly/hal-spec2 0.x 1.0 Collection+JSON
 http://bit.ly/collection-json-spec 1.0 ALPS
 http://bit.ly/alps-spec 0.x 1.0 HAL / HAL+FORMS
 http://bit.ly/hal-forms UBER
 http://bit.ly/uber-spec
  42. Unless otherwise indicated, these slides are © 2013-2019 Pivotal Software,

    Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ How to create a
 custom media type?
  43. Unless otherwise indicated, these slides are © 2013-2019 Pivotal Software,

    Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Custom media types interface HypermediaMappingInformation { List<MediaType> getMediaTypes(); default Module getJacksonModule() { … } default ObjectMapper configureObjectMapper(ObjectMapper mapper) {…} } The media types you
 want to support. A Jackson module to
 customize output OR a custom ObjectMapper
 for advanced needs
  44. Unless otherwise indicated, these slides are © 2013-2019 Pivotal Software,

    Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Custom media types public interface AffordanceModelFactory { MediaType getMediaType(); AffordanceModel getAffordanceModel(String name,
 Link link,
 HttpMethod httpMethod,
 InputPayloadMetadata inputType, List<QueryParameter> queryMethodParameters,
 PayloadMetadata outputType); } Build a media-type
 specific model
  45. Unless otherwise indicated, these slides are © 2013-2019 Pivotal Software,

    Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Custom media types META-INF/spring.factories org.sfw.hateoas.mediatype.AffordanceModelFactory=\ com.acme.MyCustomAffordanceModelFactory Look at the existing
 ones for inspiration!
  46. Unless otherwise indicated, these slides are © 2013-2019 Pivotal Software,

    Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Client support var uri = URI.create("http: //localhost:8080/api/");
 var traverson = new Traverson(uri, MediaTypes.HAL_JSON);
 var name = traverson.follow("movies", "movie", "actor")
 .withTemplateParameters(Map.of("user", 27))
 .toObject("$.name");
  47. Unless otherwise indicated, these slides are © 2013-2019 Pivotal Software,

    Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Client support var content = 
 "{'_links' : {'payment' : {'href' : '/orders/1/payment'}}}";
 var discoverer = new HalLinkDiscoverer();
 var link = discoverer.findLinkWithRel("payment", content);
 assertThat(link.getRel(), is("payment"));
 assertThat(link.getHref(), is("/orders/1/payment"));
 var discoverers = context.getBean(LinkDiscoverers.class);
 var discoverer = 
 discoverers.getLinkDiscovererFor(MediaTypes.HAL_JSON); Media-type specific
 discoverer All available ones
 in the context
  48. Klient https://github.com/odrotbohm/klient

  49. Unless otherwise indicated, these slides are © 2013-2019 Pivotal Software,

    Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Miscellaneous RepresentationModelAssembler
 I18N support in media types
 LinkRelationProvider API
 Migration script 0.x
 Kotlin extensions
  50. Unless otherwise indicated, these slides are © 2013-2019 Pivotal Software,

    Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Thanks! Oliver Drotbohm ƀ odrotbohm@pivotal.io / odrotbohm
  51. Unless otherwise indicated, these slides are © 2013-2019 Pivotal Software,

    Inc. and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Resources Rest beyond the obvious