Hypermedia APIs - GeekOut

Preview:

DESCRIPTION

REST is more than just JSON or XML over HTTP. In this presentation we take a closer look how to use links (hypermedia) when designing your RESTful API. Not only does this make your service discoverable and self-descriptive, but it also makes life easier for client developers as the business logic is simply checking the presence or absence of links (HATEOAS). The presentation is based on a sample application including real business logic and not just basic collection CRUD! We also explore what mediatypes exists for hypermedia and when to use them.

Citation preview

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

Opening gambit

http://www.worldrps.com/gambit-play

Opening gambit

http://www.worldrps.com/gambit-play

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

Recommended