Together Cheerfully to Walk with Hypermedia
Preview:
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.
Citation preview
- 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