Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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.

Oliver Drotbohm

October 09, 2019
Tweet

More Decks by Oliver Drotbohm

Other Decks in Programming

Transcript

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


    October 7–10, 2019
    Austin Convention Center

    View Slide

  2. View Slide

  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.

    View Slide

  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? !

    View Slide

  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?

    View Slide

  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

    View Slide

  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

    View Slide

  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)

    View Slide

  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

    View Slide

  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

    View Slide

  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!

    View Slide

  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?

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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
    Resource
    Collection resource

    e.g. /customers
    0.x

    View Slide

  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
    EntityRepresentationModel
    Collection resource

    e.g. /customers
    Representation Models 1.0

    View Slide

  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

    View Slide

  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

    View Slide

  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 showAll() { … }
    @GetMapping("/people/{person}")
    HttpEntity show(@PathVariable Long person) { … }
    }

    View Slide

  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

    View Slide

  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

    View Slide

  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> 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

    View Slide

  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

    View Slide

  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
    +

    View Slide

  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
    +

    View Slide

  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

    View Slide

  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

    View Slide

  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?

    View Slide

  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

    View Slide

  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?

    View Slide

  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“.

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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.

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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?

    View Slide

  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 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

    View Slide

  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 queryMethodParameters,

    PayloadMetadata outputType);
    }
    Build a media-type

    specific model

    View Slide

  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!

    View Slide

  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");

    View Slide

  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

    View Slide

  48. Klient
    https://github.com/odrotbohm/klient

    View Slide

  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

    View Slide

  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 ƀ [email protected] / odrotbohm

    View Slide

  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

    View Slide