The Cost and Benefits of Building Hypermedia APIs (with Node.js) from QconSF
Hypermedia APIs - GeekOut
-
Upload
jan-kronquist -
Category
Software
-
view
690 -
download
5
description
Transcript of Hypermedia APIs - GeekOut
Hypermedia APIs@jankronquist
Hypermedia API:s @ Jayway
Rickard Öberg (~2010)!Qi4j & Streamflow!
Usecases as your API!
Mattias Arthursson & Karl-Johan Stenflo (~2011)!JAX-RS-HATEOAS!
HATEOAS with Standard Java APIs!
Mads Enevoldsen & Jan Kronquist (~2011)!Forest (CQS conventions, sensible default links, constraints...)!
Gustaf Nilsson Kotte (~2012)!Adaptive HTML as your API
Outline
Example domain!
REST introduction (Richardson maturity model)!
Exploring media types!
html!
collection+json!
hal+json
Example domain
Rock - Paper - Scissors
http://rps.com
The future Facebook of Rock Paper Scissors!
Millions of users!
Many games per user
Playing the game
Player A
Player B
Server
rock
paperPlayer B: paper
Player A: rock
Game 123
Game 123 winner: Player B loser: Player A
CREATED WAITING
GAME WON
GAME TIED
any move
other move (victory)
other move (tie)
T
Introducing REST
Towards REST
REST architectural style!
Defined by Roy Fielding !
Richardson maturity model
http://martinfowler.com/articles/richardsonMaturityModel.html
Level 0 - Not at all RESTful
Single resource, single verb!
Examples:!
SOAP, XML-RPC, JSON-RPC
Level 0 - Example
POST /api --> { "method": “viewGame", "params": ["123"]} <-- { "result": { "state": "waiting"}, "error": null}
POST /api --> { "method": "makeMove", "params": ["123","rock"]} <-- { "result": { "state": "won", "winner": "player1", "loser": "player2"}, "error": null}
Level 0 - Analysis
HTTP as transport protocol!
Problems!No client side caching!
Tight coupling
Level 0 - In the wild
JSON/RPC over HTTP!
Why?!Simplicity!
Track all user actions!
If it ain’t broke, don't fix it
Level 1 - Resources
Many resources, single verb!/api/games/123
/api/players/player1
Almost “object oriented”
Level 1 - Example
POST /api/games/123 --> { "method": "viewGame"} <-- { "result": { "state": "waiting"}, "error": null}
POST /api/games/123 --> { "method": "makeMove", "params": ["rock"]} <-- { "result": { "state": "won", "winner": "player1", "loser": "player2"}, "error": null}
Level 1 - Analysis
Still just using HTTP as transport
Level 2 - Verbs
Resources, verb and status codes!!GET, POST, PUT, DELETE...
200 OK, 304 Not Modified, 404 Not found…
RESTish!
Uniform interface
HTTP 101
GET - safe!
POST - danger!!
PUT - idempotent!!
Note!GET will not modify the resource, but other clients might!
Interleaving other requests might break idempotency
GET /api/games/123 <-- 200 OK <-- { "state": "waiting"}
POST /api/games/123 --> { "move": "rock"} <-- 200 OK <-- { "state": "won", "winner": "player1", "loser": “player2"}
Level 2 - Example
GET /api/games/123 <-- 200 OK <-- { "state": "waiting"}
POST /api/games/123 --> Content-Type: application/json --> { "move": "rock"} <-- 200 OK <-- { "state": "won", "winner": "player1", "loser": "player2"}
Level 2 - Mediatype json
Level 2 - Mediatype form
GET /api/games/123 <-- 200 OK <-- { "state": "waiting"}
POST /api/games/123 --> Content-Type: application/x-www-form-urlencoded --> move=rock <-- 200 OK <-- { "state": "won", "winner": "player1", "loser": "player2"}
$ curl -d move=rock http://rps.com/api/games/123
Level 2 - In the wild
Verbs: GET, DELETE, PUT!
Resources: buckets, objects, acls, policies…!
Why?!CRUD operations!
GET allows caching
Level 2 - Analysis
Using HTTP, not fighting it!
Problems!Client must construct URLs!
Client must contain business rules
GET /api/games <-- 200 OK <-- { "totalCount": 23, "offset": 0, "pageSize": 10, "items": [{"id": "111", "players": ["jan", "cecilia"]}, {"id": "222", "players": ["jan", "mike"]}, {"id": "333", "players": ["cecilia", "mike"]}, ...]}
Level 2 - Constructing URLs
GET /api/games?offset=10 GET /api/games?page=2
How to navigate to page 2?
GET /api/games <-- 200 OK <-- { "totalCount": 23, "offset": 0, "pageSize": 10, "items": [{"id": "111", "players": ["jan", "cecilia"]}, {"id": "222", "players": ["jan", "mike"]}, {"id": "333", "players": ["cecilia", "mike"]}, ...]}
Level 2 - Constructing URLs
GET /api/games/{game.id}
Navigating to game
Level 2 - Business logic
GET /api/games/123 <-- 200 OK <-- { "state": "waiting", "players": ["ply1", "ply2"], "moves": { "ply1" : true} }
Should the client display the move selector?
Level 2 - Business logic solved?
GET /api/games/123 <-- 200 OK <-- { "state": "waiting", "players": ["ply1", "ply2"], "moves": { "ply1" : true}, "shouldMove": true }
Level 3 - Hypermedia Controls
Links & forms!
REST as defined by Roy Fielding
Hypermedia is defined by the presence of application control information embedded within, !or as a layer above, the presentation of information
The simultaneous presentation of information and controls such that the information becomes the affordance through which the user obtains choices and selects actions.
GET /api/games <-- 200 OK <-- { "totalCount": 23, "offset": 0, "pageSize": 10, "next": "/api/games?offset=10", "items": [{"href": "/api/games/111", "players": ["jan", "cecilia"]}, {"href": "/api/games/222", "players": ["jan", "mike"]}, {"href": "/api/games/333" "players": ["cecilia", "mike"]}, ...]}
Level 3 - Example (links)
Level 3 - Example (form)
<html> <body> <ol id="players"> <li>Jan</li> <li>Cecilia</li> </ol> <form action="/api/games/111" method="POST"> <select name="move"> <option value="rock">Rock</option> <option value="paper">Paper</option> <option value="scissors">Scissors</option> </select> <input type="submit"/> </form> </body></html>
Level 3 - Opportunity: Extra moves
<select name="move"> <option value="rock">Rock</option> <option value="paper">Paper</option> <option value="scissors">Scissors</option> <option value="lizard">Lizard</option> <option value="spock">Spock</option> </select>
Level 3 - Changing the rules
For one of the players: Flip a coin!head - he must play rock!
tail - he can play whatever he wants!
!
How can the other player take advantage of this?
http://blog.gtorangebuilder.com/2014/04/gto-brain-teaser-1-exploitation-and.html
<select name="move"> <option value="rock">Rock</option> </select>
Level 3 - In the wild
Media types & other tools
Media types
Format!application/json, text/xml, text/plain, text/csv!
Domain specific!text/vcard, text/calendar, application/calendar+json!
Hypermedia!text/html, application/xhtml+xml!
application/vnd.collection+json, application/vnd.hal+json!
application/vnd.siren+json, application/vnd.amundsen-uber+json
GET /api/games <-- 200 OK <-- Content-Type: application/json <-- { "totalCount": 23, "offset": 0, "pageSize": 10, "next": "/api/games?offset=10", ... }
json
xhtmlGET /api/games <-- 200 OK <-- Content-Type: application/xhtml+xml <--
<html> <body> <a href="/api/games?offset=10">Next</a> <div id="totalCount">23</div> <ol id="games"> <li>...</li> <li>...</li> </ol> </body></html>
collection+json
CRUD for collections of objects!
Parts of the document:!items!
links!
queries!
templates!
errors
GET /api/games <-- 200 OK <-- Content-Type: application/vnd.collection+json <-- { "collection": { "version" : "1.0", "href" : "http://rps.com/api/games", "links" : [ {"rel" : "next", "href" : "/api/games?offset=10"} ], "items" : [ { ... }, { ... } ] } }
collection+json
Anatomy of a Link
target url (href)!
human readable string (title)!
semantic information (rel)!
returned media types (type)!
!
http method (method)!
secondary key (name)!
support media types for requests (accept)
RFC 5988
Standard rels
item!
collection!
next!
edit!
enclosure!
latest-version!
self
http://www.iana.org/assignments/link-relations/link-relations.xml
Custom rels
Register with IANA :-)!
Just make up your own :-)!move!
URI!http://rps.com/rels/move!
CURIE!rps:move!
http://rps.com/rels/{rel}
Forms / templates
More than links!
Allow user input!
May provide!Default values!
Data types!
Possible values
html form
<html> <body> <ol id="players"> <li>Jan</li> <li>Cecilia</li> </ol> <form action="/api/games/111" method=“POST" name="move"> <select name="move"> <option value="rock">Rock</option> <option value="paper">Paper</option> <option value="scissors">Scissors</option> </select> <input type="submit"/> </form> </body></html>
GET /api/games/111 <-- 200 OK <-- Content-Type: application/vnd.collection+json <-- { "collection": { "version" : "1.0", "href" : "http://rps.com/api/games/111", "template" : { "data" : [{ "name" : "move", "value" : "", "prompt" : "Your move" }] } } }
collection+json template for writes
Forced resource structure
Collection of games
/api/games/
Game 111
/api/games/111
Collection of moves for game
111
/api/games/111/moves
Player 1 move for game 111
/api/games/111/moves/ply1
item
rps:moves
item
GET /api/games/111 <-- 200 OK <-- Content-Type: application/vnd.collection+json <-- { "collection": { "version" : "1.0", "href" : "http://rps.com/api/games/111", "links" : [ {"rel" : "rps:moves", "href" : "/api/games/111/moves"} ], ... } }
The game links to moves
GET /api/games/111/moves <-- 200 OK <-- Content-Type: application/vnd.collection+json <-- { "collection": { "version" : "1.0", "href" : "http://rps.com/api/games/111/moves", "template" : { "data" : { "name" : "move", "value" : "", "prompt" : "Your move" } } } }
moves collection contain template
URI templates
Constructing paths
Form-style!Making a move
Creating a new game
RFC 6570
"http://rps.com/api/games/111{?move}"
"http://rps.com/api/games/{gameId}"
"http://rps.com/api/games{?player1,player2}"
application/vnd.hal+json
Minimalistic (plain old json)!
"_links" with mandatory rels!
"_embedded" (recursive!)
GET http://rps.com/api/games/123 <-- { !!!!!!!!!!!!!! "createdAt" : "2014-05-22T16:24:01", "state" : "waiting" }
hal+json
GET http://rps.com/api/games/123 <-- { !!!!!!!! "_links": { "rps:move": { "href": "http://rps.com/api/games/123{?move}", "templated": true } } "createdAt" : "2014-05-22T16:24:01", "state" : "waiting" }
hal+json
GET http://rps.com/api/games/123 <-- { "_embedded": { "rps:player" : [{ "_links": { "self": {"href" : "http://rps.com/api/players/111"}, "name": "Jan" }, { "_links": { "self": {"href" : "http://rps.com/api/players/222"}, "name": "Cecilia" }] }, "_links": { "rps:move": { "href": "http://rps.com/api/games/123{?move}", "templated": true } } "createdAt" : "2014-05-22T16:24:01", "state" : "waiting" }
hal+json
Application state, not objects
GET http://rps.com/api/players/111/games/123 <-- { "_links": { "rps:game": {"href" : "http://rps.com/api/games/123"}, "rps:move": { "href": "http://rps.com/api/games/123{?move}", "templated": true } } }
GET http://rps.com/api/players/111/games/123 <-- { "_embedded": { "rps:game" : [{ "_links": { "self": {"href" : "http://rps.com/api/games/123"}, "createdAt" : "2014-05-22T16:24:01", "state" : "waiting" }] }, "_links": { "rps:move": { "href": "http://rps.com/api/games/123{?move}", "templated": true } } }
Application state, not objects
Summary
Hypermedia simplifies client development!No constructing of URLs!
Opportunities for less business logic!
Pick a mediatype with reasonable semantics!
Consider form-encoded when POSTing!
Some useful tools!CURIE, URITemplates