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

Building HATEOAS into your API

Building HATEOAS into your API

The focus here is on a core aspect of REST - Hypermedia and HATEOAS and share the practical steps of building this out into an existing API with Spring HATEOAS.
You’ll first learn about the basics - content negotiation, API discoverability at the root and introducing links into the responses of your API to help guide your clients.
We’ll then go into more advanced scenarios such as how we can build Hypermedia into JSOPN, how the client needs to adapt to the new information in the API, what classes of changes become non-breaking, and how to generally evolve an API.
Finally, we’ll circle back to running through a full implementation of a real-world scenario.

baeldung

June 05, 2016
Tweet

More Decks by baeldung

Other Decks in Programming

Transcript

  1. . . . . . . . . . .

    . . . Building HATEOAS into your API HI • My name is Eugen • I run a site called baeldung.com • I build APIs, mostly with Spring • Some of them actually got Hypermedia right
  2. . . . . . . . . . .

    . . . Building HATEOAS into your API • Show you why Hypermedia is important for your API • and a good way to implement it in Spring THE GOAL
  3. . . . . . . . . . .

    . . . Building HATEOAS into your API • Introducing HATEOAS AGENDA
  4. . . . . . . . . . .

    . . . Building HATEOAS into your API • Introducing HATEOAS • JSON and Hypermedia ... AGENDA
  5. . . . . . . . . . .

    . . . Building HATEOAS into your API • Introducing HATEOAS • JSON and Hypermedia ... • Simplify the Client by making the API discoverable AGENDA
  6. . . . . . . . . . .

    . . . Building HATEOAS into your API • Introducing HATEOAS • JSON and Hypermedia ... • Simplify the Client by making the API discoverable • Implementation with Spring HATEOAS and Jackson AGENDA
  7. . . . . . . . . . .

    . . . Building HATEOAS into your API • “Hypermedia as the Engine of Application State” HATEOAS
  8. . . . . . . . . . .

    . . . Building HATEOAS into your API • “Hypermedia as the Engine of Application State” • Drives the Client through the states of the system HATEOAS
  9. . . . . . . . . . .

    . . . Building HATEOAS into your API • “Hypermedia as the Engine of Application State” • Drives the Client through the states of the system • In theory – APIs should have no “out-of band” information HATEOAS
  10. . . . . . . . . . .

    . . . Building HATEOAS into your API • “Hypermedia as the Engine of Application State” • Drives the Client through the states of the system • In theory – APIs should have no “out-of band” information • In practice – APIs are not there yet (standardization, adoption) HATEOAS
  11. . . . . . . . . . .

    . . . . How do we buy a book from Amazon?
  12. . . . . . . . . . .

    . . . Building HATEOAS into your API • We find the ISBN of that book LET’S BUY A BOOK
  13. . . . . . . . . . .

    . . . Building HATEOAS into your API • We find the ISBN of that book • We go to the Amazon docs and we find the book URL pattern LET’S BUY A BOOK
  14. . . . . . . . . . .

    . . . Building HATEOAS into your API • We find the ISBN of that book • We go to the Amazon docs and we find the book URL pattern • We use the ISBN with that pattern and we get the URL of the book LET’S BUY A BOOK
  15. . . . . . . . . . .

    . . . Building HATEOAS into your API • We find the ISBN of that book • We go to the Amazon docs and we find the book URL pattern • We use the ISBN with that pattern and we get the URL of the book • We do a GET on that URL and get the book JSON LET’S BUY A BOOK
  16. . . . . . . . . . .

    . . . Building HATEOAS into your API • We find the ISBN of that book • We go to the Amazon docs and we find the book URL pattern • We use the ISBN with that pattern and we get the URL of the book • We do a GET on that URL and get the book JSON • We go back to the docs and we look for the ADD TO CART operation LET’S BUY A BOOK
  17. . . . . . . . . . .

    . . . Building HATEOAS into your API • We find the ISBN of that book • We go to the Amazon docs and we find the book URL pattern • We use the ISBN with that pattern and we get the URL of the book • We do a GET on that URL and get the book JSON • We go back to the docs and we look for the ADD TO CART operation • We then do a POST to trigger the process LET’S BUY A BOOK
  18. . . . . . . . . . .

    . . . Building HATEOAS into your API • We find the ISBN of that book • We go to the Amazon docs and we find the book URL pattern • We use the ISBN with that pattern and we get the URL of the book • We do a GET on that URL and get the book JSON • We go back to the docs and we look for the ADD TO CART operation • We then do a POST to trigger the process • Then, we go back to the docs and we look for the BUY operation LET’S BUY A BOOK
  19. . . . . . . . . . .

    . . . . Right?
  20. . . . . . . . . . .

    . . . . Right? NO!
  21. . . . . . . . . . .

    . . . . Right? NO! Definitely NOT!
  22. . . . . . . . . . .

    . . . Building HATEOAS into your API • We navigate to the book page WHAT DO WE ACTUALLY DO?
  23. . . . . . . . . . .

    . . . Building HATEOAS into your API • We navigate to the book page • We click on a LINK to add the book to the Cart WHAT DO WE ACTUALLY DO?
  24. . . . . . . . . . .

    . . . Building HATEOAS into your API • We navigate to the book page • We click on a LINK to add the book to the Cart • We click a LINK to buy the Cart WHAT DO WE ACTUALLY DO?
  25. . . . . . . . . . .

    . . . . What’s the common thread?
  26. . . . . . . . . . .

    . . . Building HATEOAS into your API • Yes, it’s the links LINKS
  27. . . . . . . . . . .

    . . . Building HATEOAS into your API • Yes, it’s the links • Simply put – Hypermedia means including Links in the Responses LINKS
  28. . . . . . . . . . .

    . . . . “Hypermedia Types are media types that contain native hyper-linking semantics that induce application flow” Mike Amundsen (2010)
  29. . . . . . . . . . .

    . . . Building HATEOAS into your API • The API uses standards/conventions with links HOW DOES THAT HELP THE CLIENT?
  30. . . . . . . . . . .

    . . . Building HATEOAS into your API • The API uses standards/conventions with links • The Client understands the conventions and navigates the system HOW DOES THAT HELP THE CLIENT?
  31. . . . . . . . . . .

    . . . Building HATEOAS into your API • The API uses standards/conventions with links • The Client understands the conventions and navigates the system • Now business logic belongs on the Server - the Client just displays it HOW DOES THAT HELP THE CLIENT?
  32. . . . . . . . . . .

    . . . Building HATEOAS into your API • The API uses standards/conventions with links • The Client understands the conventions and navigates the system • Now business logic belongs on the Server - the Client just displays it • The API can be evolved easier, without breaking Clients HOW DOES THAT HELP THE CLIENT?
  33. . . . . . . . . . .

    . . . Building HATEOAS into your API • The API uses standards/conventions with links • The Client understands the conventions and navigates the system • Now business logic belongs on the Server - the Client just displays it • The API can be evolved easier, without breaking Clients • A much better first-run experience for API integrations HOW DOES THAT HELP THE CLIENT?
  34. . . . . . . . . . .

    . . . Building HATEOAS into your API • A Hypermedia API is not enough - the Client needs to change as well THE MISSING LINK
  35. . . . . . . . . . .

    . . . Building HATEOAS into your API • A Hypermedia API is not enough - the Client needs to change as well • Public, Pixel-Perfect UI - predefined dynamic areas/zones work well THE MISSING LINK
  36. . . . . . . . . . .

    . . . Building HATEOAS into your API • A Hypermedia API is not enough - the Client needs to change as well • Public, Pixel-Perfect UI - predefined dynamic areas/zones work well • Management-Style App - action bars, overlay menus work well THE MISSING LINK
  37. . . . . . . . . . .

    . . . Building HATEOAS into your API HATEOAS ADOPTION
  38. . . . . . . . . . .

    . . . . OK, Let’s add links
  39. . . . . . . . . . .

    . . . Building HATEOAS into your API • Is HTML a Hypermedia Type? ARE WE USING A HYPERMEDIA TYPE?
  40. . . . . . . . . . .

    . . . Building HATEOAS into your API • Is HTML a Hypermedia Type? • Is XML? ARE WE USING A HYPERMEDIA TYPE?
  41. . . . . . . . . . .

    . . . Building HATEOAS into your API • Is HTML a Hypermedia Type? • Is XML? • Is JSON? ARE WE USING A HYPERMEDIA TYPE?
  42. . . . . . . . . . .

    . . . . BUT I LIKE JSON  CAN WE MAKE JSON HYPERMEDIA?
  43. . . . . . . . . . .

    . . . Building HATEOAS into your API • HAL (The Hypertext Application Language) HYPERMEDIA + JSON?
  44. . . . . . . . . . .

    . . . Building HATEOAS into your API • HAL (The Hypertext Application Language) • Collection+JSON HYPERMEDIA + JSON?
  45. . . . . . . . . . .

    . . . Building HATEOAS into your API • HAL (The Hypertext Application Language) • Collection+JSON • Siren HYPERMEDIA + JSON?
  46. . . . . . . . . . .

    . . . Building HATEOAS into your API • HAL (The Hypertext Application Language) • Collection+JSON • Siren • JSON-LD HYPERMEDIA + JSON?
  47. . . . . . . . . . .

    . . . Building HATEOAS into your API • HAL (The Hypertext Application Language) • Collection+JSON • Siren • JSON-LD • json-api HYPERMEDIA + JSON?
  48. . . . . . . . . . .

    . . . Building HATEOAS into your API • HAL (The Hypertext Application Language) • Collection+JSON • Siren • JSON-LD • json-api • And no, we’re not done HYPERMEDIA + JSON?
  49. . . . . . . . . . .

    . . . Building HATEOAS into your API • HAL (The Hypertext Application Language) • Collection+JSON • Siren • JSON-LD • json-api • And no, we’re not done: UBER HYPERMEDIA + JSON?
  50. . . . . . . . . . .

    . . . Building HATEOAS into your API • HAL (The Hypertext Application Language) • Collection+JSON • Siren • JSON-LD • json-api • And no, we’re not done: UBER, Mason HYPERMEDIA + JSON?
  51. . . . . . . . . . .

    . . . Building HATEOAS into your API • HAL (The Hypertext Application Language) • Collection+JSON • Siren • JSON-LD • json-api • And no, we’re not done: UBER, Mason, Yahapi HYPERMEDIA + JSON?
  52. . . . . . . . . . .

    . . . Building HATEOAS into your API • HAL (The Hypertext Application Language) • Collection+JSON • Siren • JSON-LD • json-api • And no, we’re not done: UBER, Mason, Yahapi, CPHL HYPERMEDIA + JSON?
  53. . . . . . . . . . .

    . . . . WE CAN DO BETTER THAN RAW JSON application/json
  54. . . . . . . . . . .

    . . . . WE CAN DO BETTER THAN RAW JSON application/json application/hal+json
  55. . . . . . . . . . .

    . . . . WE CAN DO BETTER THAN RAW JSON SPRING-HATEOAS
  56. . . . . . . . . . .

    . . . Building HATEOAS into your API • Spring HATEOAS helps you build links in your responses SPRING-HATEOAS
  57. . . . . . . . . . .

    . . . Building HATEOAS into your API • Spring HATEOAS helps you build links in your responses • Supports Hypermedia types like HAL SPRING-HATEOAS
  58. . . . . . . . . . .

    . . . Building HATEOAS into your API THE MAVEN DEPENDENCY <dependency> <groupId>org.springframework.hateoas</groupId> <artifactId>spring-hateoas</artifactId> </dependency>
  59. . . . . . . . . . .

    . . . Building HATEOAS into your API IF YOU’RE USING BOOT <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-hateoas</artifactId> </dependency>
  60. . . . . . . . . . .

    . . . Building HATEOAS into your API A NATIVE RESOURCE public class BookResource extends ResourceSupport { private long isbn; private String title; public BookResource(long isbn, String title) { this.isbn = isbn; this.title = title; } // standard getters and setters }
  61. . . . . . . . . . .

    . . . Building HATEOAS into your API A WRAPPER RESOURCE public class BookResource extends ResourceSupport { private final Book book; public BookResource(Book book) { this.book = book; } public Book getBook() { return book; } }
  62. . . . . . . . . . .

    . . . Building HATEOAS into your API A BETTER WRAPPER RESOURCE public class BookResource extends Resource<Book> { public BookResource(Book book) { super(book); } }
  63. . . . . . . . . . .

    . . . Building HATEOAS into your API A CONTROLLER @RestController @RequestMapping(value = "/books") class BookController { @Autowired private BookRepository repo; @RequestMapping(value = "/{isbn}") public BookResource findByIsbn (@PathVariable String isbn) { Book book = repo.findByIsbn(isbn); return new BookResource(book); } … }
  64. . . . . . . . . . .

    . . . Building HATEOAS into your API A SIMPLE BOOK – NO LINKS { "isbn": 1447264533, "title": "To Kill a Mockingbird", "author": "Margaret Mitchell", ... }
  65. . . . . . . . . . .

    . . . Building HATEOAS into your API • It’s good practice to include the canonical URL into a Resource THE SELF LINK
  66. . . . . . . . . . .

    . . . Building HATEOAS into your API • It’s good practice to include the canonical URL into a Resource • The Resource may be accessible on multiple paths/URIs THE SELF LINK
  67. . . . . . . . . . .

    . . . Building HATEOAS into your API • It’s good practice to include the canonical URL into a Resource • The Resource may be accessible on multiple paths/URIs • Example - as a child or an embedded Resource THE SELF LINK
  68. . . . . . . . . . .

    . . . Building HATEOAS into your API LET’S ADD A SELF LINK book.add( new Link("http://localhost/api/books/1447264533").withSelfRel() );
  69. . . . . . . . . . .

    . . . Building HATEOAS into your API LET’S ADD A SELF LINK book.add( new Link("http://localhost/api/books/1447264533").withSelfRel() ); book.add( linkToCurrentMapping().slash("/books").slash(book.getIsbn()). withSelfRel() );
  70. . . . . . . . . . .

    . . . Building HATEOAS into your API • Instead of manually building these URIs LET’S USE THE EXISTING MAPPINGS
  71. . . . . . . . . . .

    . . . Building HATEOAS into your API • Instead of manually building these URIs • Let’s point to a Controller method instead LET’S USE THE EXISTING MAPPINGS
  72. . . . . . . . . . .

    . . . Building HATEOAS into your API • Instead of manually building these URIs • Let’s point to a Controller method instead • So that we don’t have to duplicate our mappings LET’S USE THE EXISTING MAPPINGS
  73. . . . . . . . . . .

    . . . Building HATEOAS into your API A BETTER SELF LINK book.add( linkTo(BookController.class). slash(book.getIsbn()). withSelfRel() );
  74. . . . . . . . . . .

    . . . Building HATEOAS into your API A BETTER SELF LINK book.add( linkTo(BookController.class). slash(book.getIsbn()). withSelfRel() ); book.add(linkTo( methodOn(BookController.class, book).findByIsbn(book.getIsbn())). withSelfRel() );
  75. . . . . . . . . . .

    . . . Building HATEOAS into your API BOOK JSON WITH THE SELF LINK { "isbn": 1447264533, "title": "To Kill a Mockingbird“ "author": "Margaret Mitchell" }, "_links": { "self": { "href": "http://localhost/api/books/1447264533" } }
  76. . . . . . . . . . .

    . . . Building HATEOAS into your API A NEW BUY LINK bookResource.add(linkTo( methodOn(CartController.class).addBookToCart(bookResource)). withRel("add-to-cart") );
  77. . . . . . . . . . .

    . . . Building HATEOAS into your API THE JSON + THE NEW LINK { "isbn": "1447264533", "title": "Gone with the Wind", "author": "Margaret Mitchell", "_links": { "self": { "href": "http://localhost/api/books/1447264533" }, "add-to-cart": { "href": "http://localhost/api/cart" } } }
  78. . . . . . . . . . .

    . . . . LET’S NOW IMPLEMENT BUYING A BOOK
  79. . . . . . . . . . .

    . . . Building HATEOAS into your API START AT THE ROOT GET http://localhost/api/ Accept: application/json
  80. . . . . . . . . . .

    . . . Building HATEOAS into your API START AT THE ROOT { "_links": { "books": { "href": "http://localhost/books" }, "cart": { "href": "http://localhost/cart" } } }
  81. . . . . . . . . . .

    . . . Building HATEOAS into your API FOLLOW THE LINK TO ALL BOOKS GET http://localhost/api/books Accept: application/json
  82. . . . . . . . . . .

    . . . Building HATEOAS into your API ALL BOOKS [ { "isbn": "1447264533", "title": "Gone with the Wind", "links": [ { "rel": "self", "href": "http://localhost/api/books/1447264533" } ] } ]
  83. . . . . . . . . . .

    . . . Building HATEOAS into your API FOLLOW THE LINK TO A SINGLE BOOK GET http://localhost/api/books/1447264533 Accept: application/json
  84. . . . . . . . . . .

    . . . Building HATEOAS into your API A SINGLE BOOK { "isbn": "1447264533", "title": "Gone with the Wind", "_links": { "self": { … }, "buy": { "href": "http://localhost/api/cart/" } } }
  85. . . . . . . . . . .

    . . . Building HATEOAS into your API ADD THE BOOK TO THE CART POST http://localhost/api/cart Accept: application/json Content-Type: application/json { "isbn": "1447264533", "title": "Gone with the Wind" }
  86. . . . . . . . . . .

    . . . Building HATEOAS into your API THE BOOK IS IN THE CART { "books": [ { "isbn": "1447264533", "title": "Gone with the Wind" } ], "purchased": false, "_links": { "self": { "href": "http://localhost/api/cart" } } }
  87. . . . . . . . . . .

    . . . Building HATEOAS into your API LET’S PURCHASE THE CART { "books": [ { "isbn": "1447264533", "title": "Gone with the Wind" } ], "purchased": false, "_links": { "self": { "href": "http://localhost/api/cart" } } }
  88. . . . . . . . . . .

    . . . Building HATEOAS into your API • Non-standard operation – doesn’t map to any HTTP Verb PURCHASING THE CART
  89. . . . . . . . . . .

    . . . Building HATEOAS into your API • Non-standard operation – doesn’t map to any HTTP Verb • You mutate a Resource by updating it PURCHASING THE CART
  90. . . . . . . . . . .

    . . . Building HATEOAS into your API • Non-standard operation – doesn’t map to any HTTP Verb • You mutate a Resource by updating it • The Server may do all sorts of things here PURCHASING THE CART
  91. . . . . . . . . . .

    . . . Building HATEOAS into your API • Non-standard operation – doesn’t map to any HTTP Verb • You mutate a Resource by updating it • The Server may do all sorts of things here • If you do a GET right after, you may see intermediary states PURCHASING THE CART
  92. . . . . . . . . . .

    . . . Building HATEOAS into your API BUY THE CART PATCH http://localhost/api/cart Accept: application/json Content-Type: application/json { "purchased": true }
  93. . . . . . . . . . .

    . . . Building HATEOAS into your API AND WE’RE DONE { "books": [ { "isbn": "1447264533", "title": "Gone with the Wind" } ], "purchased": true, "_links": { "self": { "href": "http://localhost/api/cart" } } }
  94. . . . . . . . . . .

    . . . Building HATEOAS into your API • Common decisions – are users going to do a lot of very small calls? – are users getting huge payloads with minimal HTTP calls? API CHATTINESS AND PAYLOAD
  95. . . . . . . . . . .

    . . . . A VIEW OF A RESOURCE
  96. . . . . . . . . . .

    . . . Building HATEOAS into your API • Our Book implementation is simple LESS DATA OVER THE WIRE?
  97. . . . . . . . . . .

    . . . Building HATEOAS into your API • Our Book implementation is simple • But a real, full Book will have a lot of fields LESS DATA OVER THE WIRE?
  98. . . . . . . . . . .

    . . . Building HATEOAS into your API • Our Book implementation is simple • But a real, full Book will have a lot of fields • If we use /books and retrieve a list of books – that’s a lot of data LESS DATA OVER THE WIRE?
  99. . . . . . . . . . .

    . . . Building HATEOAS into your API • Our Book implementation is simple • But a real, full Book will have a lot of fields • If we use /books and retrieve a list of books – that’s a lot of data • When we get a list – we should get less data on each resource LESS DATA OVER THE WIRE?
  100. . . . . . . . . . .

    . . . Building HATEOAS into your API THE JACKSON @JSONVIEW public class BookView { public interface Summary { } } public class Book { @JsonView(BookView.Summary.class) private String isbn; private String author; }
  101. . . . . . . . . . .

    . . . Building HATEOAS into your API NOW, IN THE CONTROLLER @JsonView(BookView.Summary.class) @RequestMapping(method = RequestMethod.GET) public List<BookResource> findAll() { ... }
  102. . . . . . . . . . .

    . . . Building HATEOAS into your API THE FULL JSON [ { "book": { "isbn": "1447264533", "author": "Margaret Mitchell", "title": "Gone with the Wind" }, "links": [ { ... } ] } ]
  103. . . . . . . . . . .

    . . . Building HATEOAS into your API THE LIGHTER JSON [ { "book": { "isbn": "1447264533", "title": "Gone with the Wind" } } ]
  104. . . . . . . . . . .

    . . . . WE NEED MORE CONTROL
  105. . . . . . . . . . .

    . . . Building HATEOAS into your API • When we GET a Book, we get a predefined representation CAN WE CONTROL THE DATA WE GET?
  106. . . . . . . . . . .

    . . . Building HATEOAS into your API • When we GET a Book, we get a predefined representation • What about picking the fields we need from the API? CAN WE CONTROL THE DATA WE GET?
  107. . . . . . . . . . .

    . . . Building HATEOAS into your API • When we GET a Book, we get a predefined representation • What about picking the fields we need from the API? • This would be ideal: …/api/books/{isbn}?fields=title CAN WE CONTROL THE DATA WE GET?
  108. . . . . . . . . . .

    . . . Building HATEOAS into your API DEFINING A JSON FILTER public class BookResource extends ResourceSupport { ... @JsonFilter("bookFilter") public Book getBook() { return book; } }
  109. . . . . . . . . . .

    . . . Building HATEOAS into your API A JSON FILTER String[] fields = ... FilterProvider filterProvider = new SimpleFilterProvider().addFilter( "bookFilter", SimpleBeanPropertyFilter.filterOutAllExcept(fields));
  110. . . . . . . . . . .

    . . . Building HATEOAS into your API THE NEW CONTROLLER @RequestMapping(value = "/{isbn}", params = "fields") public MappingJacksonValue findByIsbnFiltered( @RequestParam String fields, @PathVariable String isbn) { ... MappingJacksonValue wrapper = new MappingJacksonValue(bookResource); wrapper.setFilters(filterProvider); return wrapper; }
  111. . . . . . . . . . .

    . . . Building HATEOAS into your API CONTROLING THE FIELDS GET http://localhost/api/books/1447264533?fields=title Accept: application/json
  112. . . . . . . . . . .

    . . . Building HATEOAS into your API THE CORRECT RESULT { "book": { "title": "Gone with the Wind" }, "links": [ { ... } ] }
  113. . . . . . . . . . .

    . . . Building HATEOAS into your API • Representing HAL _embedded Resources OTHER ADVANCED SCENARIOS
  114. . . . . . . . . . .

    . . . Building HATEOAS into your API • Representing HAL _embedded Resources • Using the Resources base class from spring-hateoas OTHER ADVANCED SCENARIOS
  115. . . . . . . . . . .

    . . . Building HATEOAS into your API • Representing HAL _embedded Resources • Using the Resources base class from spring-hateoas • With the option to either embed the full Resource OTHER ADVANCED SCENARIOS
  116. . . . . . . . . . .

    . . . Building HATEOAS into your API • Representing HAL _embedded Resources • Using the Resources base class from spring-hateoas • With the option to either embed the full Resource • Or just a Link to it OTHER ADVANCED SCENARIOS
  117. . . . . . . . . . .

    . . . . THANK YOU Blog: www.baeldung.com Twitter: @baeldung Email: [email protected] Github Repo: https://github.com/eugenp/spring-hypermedia-api