Together Cheerfully to Walk with Hypermedia

91
for Java Day Kyiv 2014 by Vladimir Tsukur KEEP CALM AND WALK CHEERFULLY WITH HYPERMEDIA

description

Primary focus of this presentation is on the hypermedia as the engine of application state (HATEOAS) and how HTTP APIs may benefit from it. Provides sneak peek into HAL media type & gives an overview of hypermedia support in Java tools (JAX-RS / HalBuilder and Spring HATEOAS) along with practical suggestions for server-side design of hypermedia API. Also includes quick overview of Richardson Maturity Model based on a set of examples, current API trends.

Transcript of Together Cheerfully to Walk with Hypermedia

  • 1. REST KEEP CALM AND WALK CHEERFULLY WITH HYPERMEDIA by Vladimir Tsukur for Java Day Kyiv 2014
  • 2. vladimir tsukur team lead @ PRINCiPAL ENGINEER @ partner @ flushdia flushdia vladimirtsukur
  • 3. Hypermedia REST 3 WEB API
  • 4. Hypermedia REST 4 Richardson Maturity Model
  • 5. Hypermedia REST 5
  • 6. Hypermedia REST 6 Level 0 - Make a Booking POST /bookings { createBooking: { room-id: kyiv:cosmopolite:std, data: { check-in: 2014-11-23, check-out: 2014-12-01, breakfast: true } } } request 200 OK { success: { id: 123, room-id: kyiv:president:std, data: } } response
  • 7. Hypermedia REST 7 Level 0 - Read Booking POST /bookings { getBooking: { id: 123 } } request 200 OK { success: { id: 123, room-id: kyiv:president:std, data: } } response
  • 8. Hypermedia REST 8 Level 0 / SOAP-like 1. Single URI endpoint: /bookings 2. Single HTTP method: POST 3. Action in payload 4. RPC-like 5. Uses HTTP as transport, not app protocol 6. Does not use mechanics of the Web Flickr SOAP & XML-RPC APIs
  • 9. Hypermedia REST 9 Level 2 - Make a Booking POST /hotels/cosmopolite/rooms/std/bookings { check-in: 2014-11-23, check-out: 2014-12-01, breakfast: true } request 200 OK { id: 123, room-id: kyiv:cosmopolite:std, check-in: } response
  • 10. Hypermedia REST 10 Level 2 - Read Booking GET /bookings/123 request 200 OK { id: 123, room-id: kyiv:cosmopolite:std, check-in: } response
  • 11. URI Template Contract Hypermedia REST 11 URL Methods /hotels GET = retrieve hotels /hotels/{id} GET = retrieve hotel /hotels/{id}/rooms/{roomId}/bookings POST = create booking /bookings GET = retrieve bookings /bookings/{id} GET = retrieve booking! PATCH = update booking! PUT = replace booking! DELETE = update booking /bookings/{id}/payment POST = pay
  • 12. Level 2 / CRUD-like 1. Many URIs, many verbs 2. Use mechanics of the Web (partially) 3. NO hypermedia Hypermedia REST 12 Amazon S3 Twitter API Google APIs
  • 13. Hypermedia REST 13 Level 3?
  • 14. Hypermedia REST 14 Take a REST https://github.com/flushdia/take-a-REST
  • 15. Hypermedia REST 15 Resource State What is stored on the server (beyond session)
  • 16. Hypermedia REST 16 Application State Where you ARE in the interaction / session Pending Confirmed Served create update rejected cancel Cancelled "live" confirmed Rejected update delete
  • 17. Hypermedia REST 17 H ypertext A s T he E ngine O f A pplication S tate
  • 18. Hypermedia REST 18 link { take-a-rest:hotel: { href: http://localhost:8080/api/hotels/2 } } URI - identifies a resource with which the consumer can interact to progress the application protocol rel - contains semantic markup (=> verb, headers, structure of the payload)
  • 19. Hypermedia REST 19 Domain Application Protocol
  • 20. Hypermedia REST 20
  • 21. Hypermedia REST 21 Booking payment N/A - to be paid on the spot N/A
  • 22. Client Hypermedia REST 22 if (booking.links.has("payment")) { // draw payment button / UI } Hypermedia client does NOT break, because it does NOT expect link to be always available
  • 23. Hypermedia REST 23 Booking service link added - new functionality N/A
  • 24. Hypermedia REST 24 Upgraded / new client MAY leverage new features when updated. ! Existing clients stay intact
  • 25. Hypermedia REST 25 Knowledge of non-hypermedia client
  • 26. Hypermedia REST 26 Knowledge of hypermedia client server leads the client media type is at the center
  • 27. Hypermedia REST 27 Client may know HOW, but NOT WHEN
  • 28. Hypermedia REST 28 profit API: explorable & self-documented Client: No URL construction No domain logic replication Less coupling Server: Transparent resource relocation Easier versioning & evolvability
  • 29. REST doesnt eliminate the need for a clue. What REST does is concentrate that need for prior knowledge into readily standardizable forms. That is the essential distinction between data-oriented and Hypermedia REST 29 control-oriented integration. Roy T. Fielding, 2008
  • 30. ... It has value because it is far easier to standardize representation and relation types than it is to standardize objects and object-specific interfaces ... Hypermedia REST Roy T. Fielding, 2008 30
  • 31. Hypermedia REST 31 cons efficiency tooling understanding by developer community
  • 32. Hypermedia REST 32 Is somebody doing hypermedia?
  • 33. Hypermedia REST 33
  • 34. A REST API should spend almost all of its descriptive effort in defining the media type(s) used for representing resources and driving application state, or in defining Hypermedia REST 34 extended relation names and/or hypertext-enabled mark-up for existing standard media types. Roy T. Fielding, 2008
  • 35. Hypermedia Factors / Control Data Support Hypermedia REST 35 IANA Link Relations Name Description RFC self Conveys an identifier for the link's context. RFC4287 first An IRI that refers to the furthest preceding resource in a series of resources. RFC5988 last An IRI that refers to the furthest following resource in a series of resources. RFC5988 up Refers to a parent document in a hierarchy of documents. RFC5988 item The target IRI points to a resource that is a member of the collection represented by the context IRI. RFC6573 collection The target IRI points to a resource which represents the collection resource for the context IRI. RFC6573 edit Refers to a resource that can be used to edit the link's context. RFC5023 prev/previous Indicates that the link's context is a part of a series, and that the previous in the series is the link target. HTML5 next Indicates that the link's context is a part of a series, and that the next in the series is the link target. HTML5
  • 36. Hypermedia Factors / Control Data Support Hypermedia REST 36 IANA Link Relations Name Description RFC create-form The target IRI points to a resource where a submission form can be obtained. RFC6861 edit-form The target IRI points to a resource where a submission form for editing associated resource can be obtained. RFC6861 payment Indicates a resource where payment is accepted RFC5988 latest-version Points to a resource containing the latest (e.g., current) version of the context. RFC5829 profile Identifying that a resource representation conforms to a certain profile, without affecting the non-profile semantics of the resource representation. RFC6906 search Refers to a resource that can be used to search through the link's context and related resources. OpenSearch index Refers to an index. HTML4 about Refers to a resource that is the subject of the link's context. RFC6903 help Refers to context-sensitive help. HTML5
  • 37. Hypermedia REST 37 Hypermedia Factors What about and ?
  • 38. Hypermedia REST JSON JSON-LD json:api HAL Cj Siren Mason Uber LE LO LT LN LI CR CU CM CL 38 JSON-based Media Types
  • 39. Hypermedia REST 39 Java Support HAL (Hypertext Application Language):! Spring HATEOAS halbuilder halarious HyperExpress-Hal + JavaScript / Scala / PHP / Ruby / C++ Siren: Siren4J JSON Hyper-Schema: JJSchema JSON-LD: json-ld-java Collection+JSON: collection-json.java (Scala support)
  • 40. Hypermedia REST 40 HAL Overview Hypertext Application Language: simple format explorable & discoverable APIs for JSON: ! application/vnd+json for XML: application/vnd+xml
  • 41. Hypermedia REST 41 HAL - state
  • 42. Hypermedia REST 42 HAL - links
  • 43. HAL - embedded resources Hypermedia REST 43
  • 44. Hypermedia REST 44 HAL - CURies Resource documentation Link name-spacing
  • 45. Hypermedia REST 45 HAL APIs
  • 46. Hypermedia REST 46 Core Domain (internal business logic) @Getter @Setter public class Booking { ! ..private Long id; ..private LocalDate checkIn; ..private LocalDate checkOut; ..private boolean includeBreakfast; ..private BigDecimal price; ..private Hotel hotel; ! }
  • 47. Hypermedia REST 47 Integration Domain (external, REST API) @Getter @Setter public class BookingRepresentation { ! ..private LocalDate checkIn; ..private LocalDate checkOut; ..private boolean includeBreakfast; ..private BigDecimal price; ..private List links; ! } http://localhost:8080/api/bookings/12345
  • 48. id not necessarily exposed in data: links encode it to preserve lookup strategy Hypermedia REST http://localhost:8080/api/bookings/12345 48 Integration Domain (external, REST API) @Getter @Setter public class BookingRepresentation { ! private LocalDate checkIn; private LocalDate checkOut; private boolean includeBreakfast; private BigDecimal price; ..private List links; ! }
  • 49. Core Domain Integration Domain Hypermedia REST 49 Different clients Different reasons to change Different rate of change IMPORTANT! Do not bridge Core domain to Integration domain (exposing it via REST API directly) to avoid coupling issues!
  • 50. Hypermedia REST 50 Embedded Resource { "_links": { }, "_embedded": { "take-a-rest:booking": { "_links": { "self": { "href": "http://~/api/bookings/12345" } }, "checkIn": [ 2014, 11, 23 ], "checkOut": [ 2014, 12, 1 ], "paid": false, "price": 4500, "hotelName": "Cosmopolite Hotel", "city": "Kyiv" } } } /api/bookings
  • 51. Hypermedia REST 51 Full Resource { "_links": { "curies": { "href": "http://~/doc/{rel}.html", "name": "take-a-rest", "templated": true }, "self": { "href": "http://~/api/bookings/1" }, "take-a-rest:booking-cancellation": { "href": "http://~/api/bookings/1" }, "take-a-rest:booking-payment": { "href": "http://~/api/bookings/1/payment" }, "take-a-rest:booking-update": { "href": "http://~/api/bookings/1" }, "take-a-rest:hotel": { "href": "http://~/api/hotels/1" } }, "checkIn": [ 2014, 11, 23 ], "checkOut": [ 2014, 12, 1 ], "paid": false, "price": 4500, "includeBreakfast": true, "roomType": "SINGLE", "hotelName": "Cosmopolite Hotel", "city": "Kyiv" } /api/bookings/12345
  • 52. Embedded / Full Resource Representations usually require different hypertext (both data and links) Provide separate assembler classes for embedded and full resource representations! Allow property expansion for granularity control: Hypermedia REST 52 /api/bookings/12345?props=checkIn,checkOut
  • 53. Hypermedia REST 53 Tooling Support 1. Media Type Representation Model Assembly 2. Link Construction 3. Documentation
  • 54. Hypermedia REST 54 HATEOAS
  • 55. @Getter @Setter public class BookingRepresentation extends ResourceSupport { ! ..private LocalDate checkIn; ! ..private LocalDate checkOut; ! ..private boolean includeBreakfast; ! ..private BigDecimal price; ! } Hypermedia HATEOAS REST 55 Representation
  • 56. BookingRepresentation representation = new BookingRepresentation(); representation.setCheckIn(LocalDate.of(2014, 11, 23)); String bookingHref = "http://~/api/bookings/" + id; representation.add(new Link(bookingHref)); // "self" link representation.add(new Link(bookingHref, "booking-update")); String hotelHref = "http://~/api/hotels/" + hotel.getId(); representation.add(new Link(hotelHref, "hotel")); Hypermedia HATEOAS REST 56 Filling Representation
  • 57. @Controller @RequestMapping("/bookings") public class BookingController { ! ..@RequestMapping(value = "/{id}", method = RequestMethod.GET) ..public HttpEntity retrieveById(@PathVariable Long id) { ....BookingRepr representation = ; ....return new HttpEntity(representation); ..} ! } Hypermedia HATEOAS REST 57 Controller
  • 58. Hypermedia HATEOAS REST 58 Filling Representation { "_links": { "hotel": { "href": "http://~/api/hotels/12345" }, "booking-update": { "href": "http://~/api/hotels/12345" } }, "checkIn": [ 2014, 11, 23 ], "checkOut": [ 2014, 12, 1 ], "includeBreakfast": true, "price": 10000 }
  • 59. Curie Provider Hypermedia HATEOAS REST 59 @Configuration @EnableWebMvc @EnableEntityLinks @EnableHypermediaSupport(type = HypermediaType.HAL) public class HypermediaConfiguration { ! ..@Bean ..public CurieProvider curieProvider() { ....return new DefaultCurieProvider( ........"take-a-rest", ........new UriTemplate("http://~/api/doc/{rel}")); ..} ! }
  • 60. Hypermedia HATEOAS REST 60 Curie Provider { "_links": { "curies": { "href": "http://~/api/doc/rels/{rel}", "name": "take-a-rest", "templated": true }, "take-a-rest:hotel": { "href": "http://~/api/hotels/12345" }, "take-a-rest:booking-update": { "href": "http://~/api/hotels/12345" }, }, }
  • 61. Resource Assembler Hypermedia HATEOAS REST 61 @Component public class BookingRepresentationAssembler extends ....ResourceAssemblerSupport { ! ..public BookingRepresentationAssembler() { ....super(BookingController.class, BookingRepr.class); ..} ! ..@Override ..public BookingRepr toResource(Booking booking) { ....BookingRepr representation = instantiateResource(booking); .... ....return representation; ..} ! }
  • 62. @Controller @RequestMapping("/bookings") public class BookingController { ! ..@Autowired ..private BookingRepresentationAssembler assembler; ! ..@RequestMapping(value = "/{id}", method = RequestMethod.GET) ..public HttpEntity retrieveById(@PathVariable Long id) { ....Booking booking = ; ....BookingRepr representation = assembler.toResource(booking); ....return new HttpEntity(representation); ..} ! } Hypermedia HATEOAS REST 62 Controller -> Assembler
  • 63. JAX-RS REST @Path("/bookings") public class BookingResource { ! ..@GET ..@Path("/{id}") ..public BookingRepr retrieveById(@PathParam("id") Long id) { ....BookingRepr representation = ; ....return representation; ..} ! } Hypermedia 63 Resource
  • 64. RepresentationFactory factory = new StandardRepresentationFactory(); Representation repr = factory.newRepresentation().withBean(sample); repr.toString(RepresentationFactory.HAL_JSON); Hypermedia REST 64 { "checkIn": [ 2014, 11, 23 ], "checkOut": [ 2014, 12, 1 ], "includeBreakfast": true, "price": 10000 }
  • 65. Hypermedia REST 65 factory.newRepresentation(). ....withBean(sample). ....withProperty("paid", false); { "checkIn": [ 2014, 11, 23 ], "checkOut": [ 2014, 12, 1 ], "includeBreakfast": true, "price": 10000, "paid": false }
  • 66. factory.newRepresentation() ........withNamespace("take-a-rest", "http://~/api/doc/rels/{rel}"). ........withLink("take-a-rest:hotel", "http://~/api/hotels/12345"); Hypermedia REST 66 { "_links": { "curies": { "href": "http://~/api/doc/rels/{rel}", "name": "take-a-rest", "templated": true }, "take-a-rest:hotel": { "href": "http://~/api/hotels/12345" } }, }
  • 67. factory.newRepresentation() ........withRepresentation("take-a-rest:booking", ................factory.newRepresentation().withBean(sampleBooking)); Hypermedia REST 67 { "_embedded": { "take-a-rest:booking": { "checkIn": [ 2014, 11, 23 ], "checkOut": [ 2014, 12, 1 ], "includeBreakfast": true, "price": 10000 } } }
  • 68. XML support via halbuilder-xml JAX-RS support via halbuilder-jaxrs Scala support via halbuilder-scala Representation reader API Modular Open for customization Agnostic to web framework (JAX-RS / Spring / etc.) Hypermedia REST 68
  • 69. setting self link: RepresentationFactory.newRepresentation(String/URI) field mapping: Representation.withFields mapping delegation: Representation.withRepresentable serialization options: pretty print, null stripping, collating links, etc. custom Jacksons ObjectMapper configuration available via custom RepresentationFactory option to serialize directly to java.io.Writer link rel validation Hypermedia REST 69
  • 70. Hypermedia REST 70 Link Construction: How? based on the request: scheme server port { "_links": { "take-a-rest:booking": { "href": "http://localhost:8080/api/bookings/12345" } }, } root URL rel. resource URL based on configuration:
  • 71. @Controller @RequestMapping("/bookings") public class BookingController { ! ..@RequestMapping(value = "/{id}", method = RequestMethod.GET) ..public HttpEntity retrieveById(@PathVariable Long id) { ....BookingRepr representation = ; ....// add links ....return new HttpEntity(representation); ..} ! } Hypermedia HATEOAS REST 71 Controller /api/bookings /api/bookings/{id}
  • 72. Not a Go Hypermedia HATEOAS REST 72 representation.add( ....new Link("http://localhost:8080/api/bookings/" + id) ); - does not respect request protocol, host and port - may not URL-escape id properly if it is a String - breaks if root URL changes - breaks if controller URL mappings change
  • 73. import static org.springControllerLinkBuilder.linkTo; ! representation.add( ....linkTo(BookingsController.class).slash(id).withSelfRel() ); http://localhost:8080/api/bookings /12345 Hypermedia HATEOAS REST 73 Standard Approach - breaks if controller URL mappings change
  • 74. import static org.springControllerLinkBuilder.linkTo; ! try { ..Method method = BookingsResource.class.getMethod("retrieveById", Long.class); ..representation.add( ....linkTo(BookingsResource.class, method, booking.getId()).withSelfRel() ..); } catch (NoSuchMethodException e) { } Hypermedia HATEOAS REST 74 Reflective Approach http://localhost:8080/api/bookings/12345 - breaks if method is renamed or parameters are changed - much more boilerplate :(
  • 75. import static org.springControllerLinkBuilder.linkTo; import static org.springControllerLinkBuilder.methodOn; ! representation.add( ....linkTo(methodOn(BookingsController.class).retrieveById(id)).withSelfRel() ); Hypermedia HATEOAS REST 75 Type-safe http://localhost:8080/api/bookings/12345 (probably) most convenient approach - return type must be proxy-able - non-@PathVariable parameters are neglected
  • 76. JAX-RS REST /api/bookings @Path("/bookings") public class BookingResource { ! ..@GET ..@Path("/{id}") ..public BookingRepr retrieveById(@PathParam("id") Long id) { ....BookingRepr representation = ....// add links ....return representation; ..} ! } Hypermedia /api/bookings/{id} 76 Resource
  • 77. JAX-RS REST Hypermedia 77 UriBuilder #1 @GET @Path("/{id}") public BookingRepr retrieveById(, @Context UriInfo uri) { ..representation.withLink( ...."self", ....uri.getBaseUriBuilder(). ......path(BookingsResource.class). ......segment(id.toString()). ......build() ..); } http://localhost:8080/api /bookings /12345 - breaks if resource URL mappings change
  • 78. JAX-RS REST try { ..Method method = BookingsResource.class.getMethod( ...."retrieveById", Long.class, UriInfo.class); ..representation.withLink( ...."self", ....uri.getBaseUriBuilder(). ......path(BookingsResource.class). ......path(method). ......build(id.toString()) ..); } catch (NoSuchMethodException e) { .. } - does not make things robust Hypermedia 78 - cumbersome UriBuilder #2
  • 79. JAX-RS REST UriBuilder #3 Hypermedia 79 representation.withLink( .."self", ..uri.getBaseUriBuilder(). ....path(BookingsResource.class). ....path(BookingsResource.class, "retrieveById"). ....build(id.toString()) ); - breaks if method is renamed or path parameters are changed
  • 80. JAX-RS REST Hypermedia 80 - URI construction could have been more robust!
  • 81. JAX-RS REST Hypermedia 81 LinkBuilder return Response.ok(representation). ....link("http://~/api/hotels/1", "hotel"). ....build(); 200 OK HTTP/1.1 Content-Type: application/json Link: ; rel="hotel" { "checkIn": [ 2014, 11, 23 ], "checkOut": [ 2014, 12, 1 ], "includeBreakfast": true, "price": 10000, "paid": false }
  • 82. Hypermedia REST 82 CURie Documentation? Use Markdown!
  • 83. Hypermedia REST 83 API Survey Spring 2014 180+ respondents
  • 84. Hypermedia REST 84 API Survey - Top Priority Security Usability Can't decide 18 % 38 % 44 %
  • 85. Hypermedia REST 85 API Survey - Format JSON XML Other 2 % 48 % 51 %
  • 86. Hypermedia REST 86 API Survey - Style (Now) SOAP CRUD Hypermedia 24 % 39 % 38 %
  • 87. Hypermedia REST 87 API Survey - Plans to add 28 % 21 % 14 % 7 % 0 % Hypermedia SOAP CRUD
  • 88. REST 88 Thanks! Questions?
  • 89. References - Hypermedia & APIs https://www.mnot.net/blog/2013/06/23/linking_apis http://oredev.org/2010/sessions/hypermedia-apis http://vimeo.com/75106815 https://www.innoq.com/blog/st/2012/06/hypermedia-benefits-for-m2m-communication/ http://ws-rest.org/2014/sites/default/files/wsrest2014_submission_12.pdf http://www.infoq.com/news/2014/03/ca-api-survey https://twitter.com/hypermediaapis https://www.youtube.com/watch?v=hdSrT4yjS1g https://www.youtube.com/watch?v=mZ8_QgJ5mbs http://nordsc.com/ext/classification_of_http_based_apis.html http://soabits.blogspot.no/2013/12/selling-benefits-of-hypermedia.html https://github.com/mamund/Building-Hypermedia-APIs http://amundsen.com/hypermedia/hfactor/ http://tech.blog.box.com/2013/04/get-developer-hugs-with-rich-error-handling-in-your-api/ http://odino.org/hypermedia-services-beyond-rest-architectures/ Hypermedia REST 89
  • 90. References - Media Types http://stateless.co/hal_specification.html https://github.com/kevinswiber/siren https://github.com/JornWildt/Mason http://json-ld.org/ http://amundsen.com/media-types/collection/ http://soabits.blogspot.com/2013/12/media-types-for-apis.html http://soabits.blogspot.no/2013/05/the-role-of-media-types-in-restful-web. Hypermedia REST 90 html http://soabits.blogspot.com/2014/03/modelling-shipment-example-as. html http://soabits.blogspot.com/2014/02/representing-issue-tracker-with-mason. html https://github.com/mamund/media-types/blob/master/uber-hypermedia. asciidoc
  • 91. References - Tutorials & Tools https://jax-rs-spec.java.net/ http://www.oracle.com/technetwork/articles/java/jaxrs20-1929352.html http://resteasy.jboss.org/ https://code.google.com/p/siren4j/ http://gotohal.net/ https://www.youtube.com/watch?v=1wEp9yHHtwg https://www.youtube.com/watch?v=sVvL12BnIyQ https://www.youtube.com/watch?v=pCnXy2Hs2Ag https://www.youtube.com/watch?v=_0kmqtWYvaY http://kingsfleet.blogspot.com/2014/02/transparent-patch-support-in-jax-rs-20.html http://spring.io/guides/tutorials/rest/ https://jaxb.java.net/ https://github.com/FasterXML/jackson http://zeroturnaround.com/rebellabs/beyond-rest-how-to-build-a-hateoas-api-in-java-with-spring-jax-rs-and-vraptor/ Hypermedia REST 91