2016Vladimir Tsukur @ Tech Lunch
hypermedia APIs
and <HATEOAS>
REST
Volodymyr Tsukur partner @
software
engineering
manager @
flushdia vtsukur
program
committee @
Web / HTTP API
Richardson Maturity Model
Идея! ЧЕРНЫЙ
РЫНОК ВАЛЮТ!
6
DEMO TIME !
9
Method URL Task
POST /ads Create new ad
GET /ads View ads
GET /ads/{id} Get ad
PATCH / PUT /ads/{id} Update ad
DELETE /ads/{id} Delete ad
CRUD Style API
Должен же быть
БИЗНЕС-
ПРОЦЕСС!
if (status == Status.NEW) {
publishedAt = LocalDateTime.now();
status = Status.PUBLISHED;
} …
CRUD is NOT enough
12
Method URL Task
PUT /ads/{id}/publishing Publish ad
PUT /ads/{id}/expiration Expire ad
GET /ads/search/published Get published ads
API Changes
DEMO TIME !
/uri Style Adoption?
43%
Richardson Maturity Model
16
17
Task Method URL
Update ad PATCH /ads/{id}
Delete ad DELETE /ads/{id}
Publish ad PUT /ads/{id}/publishing
Expire ad PUT /ads/{id}/expiration
URL Binding & Construction
URL Change Drivers
URL Change Drivers
•monolith → micro-services
•deployment requirements / proxies
•renaming
•optimization by locality
•caching
•…
20
Task Method URL
Update ad
(only if NEW) PATCH /ads/{id}
Delete ad
(only if NEW) DELETE /ads/{id}
Publish ad
(only if NEW) PUT /ads/{id}/publishing
Expire ad
(only if
PUBLISHED)
PUT /ads/{id}/expiration
"Figuring" Out the Flow
Security!
22
Task Method URL
Update ad
(only if NEW and user
has permissions)
PATCH /ads/{id}
Delete ad
(only if NEW and user
has permissions)
DELETE /ads/{id}
Publish ad
(only if NEW and user
has permissions)
PUT /ads/{id}/publishing
Expire ad
(only if PUBLISHED and
user has permissions)
PUT /ads/{id}/expiration
Security!
Должно работать
на моем iPhone 6s
!!!!!
"Hypermedia" =
{
"amount": 3000,
"currency": "USD",
…
}
data
{
…
"_links": {
"publishing": {
"href": "/ads/1/publishing"
},
"update": {
"href": "/ads/1"
},
"deletion": {
"href": "/ads/1"
}
}
}
links
+
26
Link Relation Task Method
update Update ad PATCH
deletion Delete ad DELETE
publishing Publish ad PUT
expiration Expire ad PUT
Hypermedia API
DEMO TIME !
Hypermedia Client
if (ad._links.has("publishing")) {
// draw publishing button / UI
}
"Simple" Hypermedia
✓where to go?
✓when?
- how?
Non-Hypermedia Client
Hypermedia Client
"I want hypermedia!" (2014)
0 %
7 %
14 %
21 %
28 %
Hypermedia SOAP CRUD
40%
"I want hypermedia!" (2015)
Давай глубже!
«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 extended relation
names and/or hypertext-enabled mark-up for
existing standard media types.»
Roy T. Fielding, 2008
Hypertext Application Language
Mason{
"amount": 3000,
"currency": "USD",
"rate": 25.8,
…
"@controls": {
"user": {
"href": "/users/1536c64"
},
"ad-publish": {
"href": "/ads/1536c64/publishing",
"method": "PUT"
}
}
}
Hypermedia FactorsHypermedia Factors
Pokemons
level of hypermedia support
CL = Link Semantics
"_links": {
"user": {
"href": "/users/1"
}
}
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
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
CL = Link Semantics
<link rel="stylesheet" src="styles.css" />
LE = Link Outbound
"_links": {
"ad": {
"href": "/ads/1536c64"
}
}
LE = Link Outbound
<a href="/ads/1536c64.html">
Buy, Rate: 25.0, USD 3000
</a>
LE = Link Embedded
"_embedded": {
"ad": [
{ "id": "5d75544", … },
{ "id": "1448cf9", … },
]
}
LE = Link Embedded
<img src="/images/cities/lviv.jpg">
LT = Templated Queries
"_links": {
"ad-by-id": {
"href": "/ads{/id}",
"templated": true
}
}
LT = Templated Queries
"_links": {
"ads": {
"href": "/ads{?page,size,sort}",
"templated": true
}
}
RFC 6570
LT = Templated Queries
<form method="get" action="/hotels/search">
<input name="query" type="text">
<input type="submit">
</form>
LN = Non-Idemp. Updates
"actions": [
{
"name": "create-ad",
"method": "POST",
"href": "/ads",
"type": "application/json",
"fields": [
{ "name": "type", "type": "text" },
{ "name": "amount", "type": "number" },
{ "name": "currency", "type": "text" },
…
]
}
]
<form method="post" action="/ads">
<input name="type" type="text">
<input name="amount" type="number">
<input name="currency" type="text">
<input type="submit">
</form>
LN = Non-Idemp. Updates
LN = Idempotent Updates
"actions": [
{
"name": "delete-ad",
"method": "DELETE",
"href": "/ads/1d2e9f5"
}
]
CM = Method Modification
"actions": [
{
"name": "delete-ad",
"method": "DELETE",
"href": "/ads/1"
}
]
CM = Method Modification
"actions": [
{
"name": "delete-ad",
"method": "DELETE",
"href": "/ads/1"
}
]
CR = Read Modification
"_links": {
"ad": [
{
"name": "ad-json",
"href": "/ads/1536c64",
"type": "application/json"
}
{
"name": "ad-xml",
"href": "/ads/1536c64",
"type": "application/xml"
}
]
}
Hypermedia Factors
?What about and
Hypermedia Factors
HTML XML JSON
LE ⩗ ⊗ ⊗
LO ⩗ ⊗ ⊗
LT ⩗ ⊗ ⊗
LN ⩗ ⊗ ⊗
LI ⊗ ⊗ ⊗
CR ⊗ ⊗ ⊗
CU ⩗ ⊗ ⊗
CM ⩗ ⊗ ⊗
CL ⩗ ⊗ ⊗
JSON-based Media Types
JSON-LD JSON API HAL Cj Siren Mason Uber
LE ⩗ ⩗ ⩗ ⩗ ⩗ ⩗ ⩗
LO ⩗ ⩗ ⩗ ⩗ ⩗ ⩗ ⩗
LT ⊗ ⩗ ⩗ ⩗ ⊗ ⩗ ⩗
LN ⊗ ⊗ ⊗ ⩗ ⩗ ⩗ ⩗
LI ⊗ ⊗ ⊗ ⩗ ⩗ ⩗ ⩗
CR ⊗ ⊗ ⩗ ⊗ ⩗ ⩗ ⩗
CU ⊗ ⊗ ⊗ ⊗ ⩗ ⩗ ⩗
CM ⊗ ⊗ ⊗ ⊗ ⩗ ⩗ ⩗
CL ⩗ ⩗ ⩗ ⩗ ⩗ ⩗ ⩗
Before Choosing Media Type
•list information client would want
from the API
•draw state diagram
-think "tasks"
-"(non-)idempotent", "(un-)safe"
{
"@id": "http://black-market.com/ads/1536c64",
"@context": "http://black-market.com/schema/ad.jsonld",
"amount": 3000,
"currency": "USD",
"rate": 25.8,
…
}
JSON-LD
{
"@context": {
"amount": "https://schema.org/amount",
"currency": "https://schema.org/currency",
"rate": {
"@id": "http://black-market.com/schema/rate",
"@type": "xsd:double"
},
…
}
}
http://black-market.com/schema/ad.jsonld
{
"@id": "http://black-market.com/ads/1536c64",
…
"supportedOperations": [
{
"@type": "PublishAdOperation",
"method": "PUT",
"expects": "#Ad"
}
]
}
JSON-LD + Hydra
{
"class": [ "ad" ],
"properties": {
"amount": 3000,
"currency": "USD",
…
},
"links": …,
"actions": [
{
"name": "create-ad",
"href": "/ads",
"method": "POST",
…
}
]
}
SIREN
CL = Link Semantics
"_links": {
"user": {
"href": "/users/1"
}
}
CL = Link Semantics
"_links": {
"http://black-market.com/rels/user": {
"href": "/users/1"
}
}
CL = Link Semantics
"_links": {
"urn:black-market.com/rels/user": {
"href": "/users/1"
}
}
CL = Link Semantics
"_links": {
"black-market:user": {
"href": "/users/1"
}
}
CompactURIE
black-market=
http://black-market.com/rels/{rel}
black-market:user
http://black-market.com/rels/user
DEMO TIME !
Mason{
"amount": 3000,
"currency": "USD",
"rate": 25.8,
…
"@namespaces": {
"black-market": { "name": "http://black-market.com/rels/" }
},
"@controls": {
"black-market:user": { "href": "/users/1536c64" },
"black-market:ad-publish": {
"href": "/ads/1536c64/publishing",
"method": "POST"
}
}
}
А документация?
Documentation
•no URLs except of the entry point
•resources
•links (namespaces)
•HTTP status codes, verbs
•authentication, rate limiting
•errors
DEMO TIME !
Profiles
✓where to go?
✓when?
✓how?
DEMO TIME !
78
Thanks!
Questions?
References
1. http://www.google.com.ua/trends/explore#q=web%20api%2C%20rest%20api&cmpt=q&tz=
2. http://finance.i.ua/market/
3. http://projects.spring.io/spring-boot/
4. http://projects.spring.io/spring-data/
5. http://docs.spring.io/spring-data/jpa/docs/1.7.2.RELEASE/reference/html/
6. http://projects.spring.io/spring-data-rest/
7. http://docs.spring.io/spring-data/rest/docs/2.3.0.RELEASE/reference/html/
8. https://spring.io/blog/2014/07/14/spring-data-rest-now-comes-with-alps-metadata
9. http://projects.spring.io/spring-hateoas/
10. http://docs.spring.io/spring-hateoas/docs/0.17.0.RELEASE/reference/html/
11. https://github.com/spring-projects/spring-restdocs
12. https://blog.akana.com/hypermedia-apis
13. http://www.apiacademy.co/lessons/api-design/web-api-architectural-styles
14. http://www.programmableweb.com/news/modern-api-architectural-styles-offer-developers-choices/2014/06/13
15. https://en.wikipedia.org/wiki/Hypermedia
16. http://stateless.co/hal_specification.html
17. https://github.com/kevinswiber/siren
18. https://www.mnot.net/blog/2013/06/23/linking_apis
19. http://oredev.org/2010/sessions/hypermedia-apis
20. http://vimeo.com/75106815
21. https://www.innoq.com/blog/st/2012/06/hypermedia-benefits-for-m2m-communication/
22. http://ws-rest.org/2014/sites/default/files/wsrest2014_submission_12.pdf
23. http://www.infoq.com/news/2014/03/ca-api-survey
24. https://twitter.com/hypermediaapis
25. https://www.youtube.com/watch?v=hdSrT4yjS1g
26. https://www.youtube.com/watch?v=mZ8_QgJ5mbs
27. http://nordsc.com/ext/classification_of_http_based_apis.html
28. http://soabits.blogspot.no/2013/12/selling-benefits-of-hypermedia.html
29. https://github.com/mamund/Building-Hypermedia-APIs
30. http://tech.blog.box.com/2013/04/get-developer-hugs-with-rich-error-handling-in-your-api/

Hypermedia APIs and HATEOAS

  • 1.
    2016Vladimir Tsukur @Tech Lunch hypermedia APIs and <HATEOAS>
  • 2.
    REST Volodymyr Tsukur partner@ software engineering manager @ flushdia vtsukur program committee @
  • 3.
  • 4.
  • 5.
  • 6.
  • 8.
  • 9.
    9 Method URL Task POST/ads Create new ad GET /ads View ads GET /ads/{id} Get ad PATCH / PUT /ads/{id} Update ad DELETE /ads/{id} Delete ad CRUD Style API
  • 10.
  • 11.
    if (status ==Status.NEW) { publishedAt = LocalDateTime.now(); status = Status.PUBLISHED; } … CRUD is NOT enough
  • 12.
    12 Method URL Task PUT/ads/{id}/publishing Publish ad PUT /ads/{id}/expiration Expire ad GET /ads/search/published Get published ads API Changes
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
    17 Task Method URL Updatead PATCH /ads/{id} Delete ad DELETE /ads/{id} Publish ad PUT /ads/{id}/publishing Expire ad PUT /ads/{id}/expiration URL Binding & Construction
  • 18.
  • 19.
    URL Change Drivers •monolith→ micro-services •deployment requirements / proxies •renaming •optimization by locality •caching •…
  • 20.
    20 Task Method URL Updatead (only if NEW) PATCH /ads/{id} Delete ad (only if NEW) DELETE /ads/{id} Publish ad (only if NEW) PUT /ads/{id}/publishing Expire ad (only if PUBLISHED) PUT /ads/{id}/expiration "Figuring" Out the Flow
  • 21.
  • 22.
    22 Task Method URL Updatead (only if NEW and user has permissions) PATCH /ads/{id} Delete ad (only if NEW and user has permissions) DELETE /ads/{id} Publish ad (only if NEW and user has permissions) PUT /ads/{id}/publishing Expire ad (only if PUBLISHED and user has permissions) PUT /ads/{id}/expiration Security!
  • 23.
  • 25.
    "Hypermedia" = { "amount": 3000, "currency":"USD", … } data { … "_links": { "publishing": { "href": "/ads/1/publishing" }, "update": { "href": "/ads/1" }, "deletion": { "href": "/ads/1" } } } links +
  • 26.
    26 Link Relation TaskMethod update Update ad PATCH deletion Delete ad DELETE publishing Publish ad PUT expiration Expire ad PUT Hypermedia API
  • 27.
  • 28.
    Hypermedia Client if (ad._links.has("publishing")){ // draw publishing button / UI }
  • 29.
  • 30.
  • 31.
  • 32.
    "I want hypermedia!"(2014) 0 % 7 % 14 % 21 % 28 % Hypermedia SOAP CRUD
  • 33.
  • 35.
  • 36.
    «A REST APIshould spend almost all of its descriptive effort in defining the media type(s) used for representing resources and driving application state, or in defining extended relation names and/or hypertext-enabled mark-up for existing standard media types.» Roy T. Fielding, 2008
  • 37.
  • 38.
    Mason{ "amount": 3000, "currency": "USD", "rate":25.8, … "@controls": { "user": { "href": "/users/1536c64" }, "ad-publish": { "href": "/ads/1536c64/publishing", "method": "PUT" } } }
  • 39.
  • 40.
    CL = LinkSemantics "_links": { "user": { "href": "/users/1" } }
  • 41.
    IANA Link Relations NameDescription 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
  • 42.
    IANA Link Relations NameDescription 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
  • 43.
    CL = LinkSemantics <link rel="stylesheet" src="styles.css" />
  • 44.
    LE = LinkOutbound "_links": { "ad": { "href": "/ads/1536c64" } }
  • 45.
    LE = LinkOutbound <a href="/ads/1536c64.html"> Buy, Rate: 25.0, USD 3000 </a>
  • 46.
    LE = LinkEmbedded "_embedded": { "ad": [ { "id": "5d75544", … }, { "id": "1448cf9", … }, ] }
  • 47.
    LE = LinkEmbedded <img src="/images/cities/lviv.jpg">
  • 48.
    LT = TemplatedQueries "_links": { "ad-by-id": { "href": "/ads{/id}", "templated": true } }
  • 49.
    LT = TemplatedQueries "_links": { "ads": { "href": "/ads{?page,size,sort}", "templated": true } }
  • 50.
  • 51.
    LT = TemplatedQueries <form method="get" action="/hotels/search"> <input name="query" type="text"> <input type="submit"> </form>
  • 52.
    LN = Non-Idemp.Updates "actions": [ { "name": "create-ad", "method": "POST", "href": "/ads", "type": "application/json", "fields": [ { "name": "type", "type": "text" }, { "name": "amount", "type": "number" }, { "name": "currency", "type": "text" }, … ] } ]
  • 53.
    <form method="post" action="/ads"> <inputname="type" type="text"> <input name="amount" type="number"> <input name="currency" type="text"> <input type="submit"> </form> LN = Non-Idemp. Updates
  • 54.
    LN = IdempotentUpdates "actions": [ { "name": "delete-ad", "method": "DELETE", "href": "/ads/1d2e9f5" } ]
  • 55.
    CM = MethodModification "actions": [ { "name": "delete-ad", "method": "DELETE", "href": "/ads/1" } ]
  • 56.
    CM = MethodModification "actions": [ { "name": "delete-ad", "method": "DELETE", "href": "/ads/1" } ]
  • 57.
    CR = ReadModification "_links": { "ad": [ { "name": "ad-json", "href": "/ads/1536c64", "type": "application/json" } { "name": "ad-xml", "href": "/ads/1536c64", "type": "application/xml" } ] }
  • 58.
  • 59.
    Hypermedia Factors HTML XMLJSON LE ⩗ ⊗ ⊗ LO ⩗ ⊗ ⊗ LT ⩗ ⊗ ⊗ LN ⩗ ⊗ ⊗ LI ⊗ ⊗ ⊗ CR ⊗ ⊗ ⊗ CU ⩗ ⊗ ⊗ CM ⩗ ⊗ ⊗ CL ⩗ ⊗ ⊗
  • 60.
    JSON-based Media Types JSON-LDJSON API HAL Cj Siren Mason Uber LE ⩗ ⩗ ⩗ ⩗ ⩗ ⩗ ⩗ LO ⩗ ⩗ ⩗ ⩗ ⩗ ⩗ ⩗ LT ⊗ ⩗ ⩗ ⩗ ⊗ ⩗ ⩗ LN ⊗ ⊗ ⊗ ⩗ ⩗ ⩗ ⩗ LI ⊗ ⊗ ⊗ ⩗ ⩗ ⩗ ⩗ CR ⊗ ⊗ ⩗ ⊗ ⩗ ⩗ ⩗ CU ⊗ ⊗ ⊗ ⊗ ⩗ ⩗ ⩗ CM ⊗ ⊗ ⊗ ⊗ ⩗ ⩗ ⩗ CL ⩗ ⩗ ⩗ ⩗ ⩗ ⩗ ⩗
  • 61.
    Before Choosing MediaType •list information client would want from the API •draw state diagram -think "tasks" -"(non-)idempotent", "(un-)safe"
  • 62.
  • 63.
    { "@context": { "amount": "https://schema.org/amount", "currency":"https://schema.org/currency", "rate": { "@id": "http://black-market.com/schema/rate", "@type": "xsd:double" }, … } } http://black-market.com/schema/ad.jsonld
  • 64.
    { "@id": "http://black-market.com/ads/1536c64", … "supportedOperations": [ { "@type":"PublishAdOperation", "method": "PUT", "expects": "#Ad" } ] } JSON-LD + Hydra
  • 65.
    { "class": [ "ad"], "properties": { "amount": 3000, "currency": "USD", … }, "links": …, "actions": [ { "name": "create-ad", "href": "/ads", "method": "POST", … } ] } SIREN
  • 66.
    CL = LinkSemantics "_links": { "user": { "href": "/users/1" } }
  • 67.
    CL = LinkSemantics "_links": { "http://black-market.com/rels/user": { "href": "/users/1" } }
  • 68.
    CL = LinkSemantics "_links": { "urn:black-market.com/rels/user": { "href": "/users/1" } }
  • 69.
    CL = LinkSemantics "_links": { "black-market:user": { "href": "/users/1" } }
  • 70.
  • 71.
  • 72.
    Mason{ "amount": 3000, "currency": "USD", "rate":25.8, … "@namespaces": { "black-market": { "name": "http://black-market.com/rels/" } }, "@controls": { "black-market:user": { "href": "/users/1536c64" }, "black-market:ad-publish": { "href": "/ads/1536c64/publishing", "method": "POST" } } }
  • 73.
  • 74.
    Documentation •no URLs exceptof the entry point •resources •links (namespaces) •HTTP status codes, verbs •authentication, rate limiting •errors
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
    References 1. http://www.google.com.ua/trends/explore#q=web%20api%2C%20rest%20api&cmpt=q&tz= 2. http://finance.i.ua/market/ 3.http://projects.spring.io/spring-boot/ 4. http://projects.spring.io/spring-data/ 5. http://docs.spring.io/spring-data/jpa/docs/1.7.2.RELEASE/reference/html/ 6. http://projects.spring.io/spring-data-rest/ 7. http://docs.spring.io/spring-data/rest/docs/2.3.0.RELEASE/reference/html/ 8. https://spring.io/blog/2014/07/14/spring-data-rest-now-comes-with-alps-metadata 9. http://projects.spring.io/spring-hateoas/ 10. http://docs.spring.io/spring-hateoas/docs/0.17.0.RELEASE/reference/html/ 11. https://github.com/spring-projects/spring-restdocs 12. https://blog.akana.com/hypermedia-apis 13. http://www.apiacademy.co/lessons/api-design/web-api-architectural-styles 14. http://www.programmableweb.com/news/modern-api-architectural-styles-offer-developers-choices/2014/06/13 15. https://en.wikipedia.org/wiki/Hypermedia 16. http://stateless.co/hal_specification.html 17. https://github.com/kevinswiber/siren 18. https://www.mnot.net/blog/2013/06/23/linking_apis 19. http://oredev.org/2010/sessions/hypermedia-apis 20. http://vimeo.com/75106815 21. https://www.innoq.com/blog/st/2012/06/hypermedia-benefits-for-m2m-communication/ 22. http://ws-rest.org/2014/sites/default/files/wsrest2014_submission_12.pdf 23. http://www.infoq.com/news/2014/03/ca-api-survey 24. https://twitter.com/hypermediaapis 25. https://www.youtube.com/watch?v=hdSrT4yjS1g 26. https://www.youtube.com/watch?v=mZ8_QgJ5mbs 27. http://nordsc.com/ext/classification_of_http_based_apis.html 28. http://soabits.blogspot.no/2013/12/selling-benefits-of-hypermedia.html 29. https://github.com/mamund/Building-Hypermedia-APIs 30. http://tech.blog.box.com/2013/04/get-developer-hugs-with-rich-error-handling-in-your-api/