Slide 1

Slide 1 text

APIs Hypermedia Olivier Hervieu — Pycon FR — 26/10/2014

Slide 2

Slide 2 text

About me • head of software at • @_olivier_

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

1 - REST REpresentational State Transfer

Slide 5

Slide 5 text

• Architectural Styles and the Design of Network-based Software Architectures — Roy Fielding — 2000 • A RESTful architecture is an architecture that respects the following 6 constraints: • Client-Server • Stateless • Cacheable • Uniform Interface • Layered System • Code on Demand (optionally)

Slide 6

Slide 6 text

Client-Server By separating the user interface concerns from the data storage concerns, we improve the portability of the user interface across multiple platforms and improve scalability by simplifying the server components.

Slide 7

Slide 7 text

Stateless Communication must be stateless in nature […] such that each request from client to server must contain all of the information necessary to understand the request, and cannot take advantage of any stored context on the server. Session state is therefore kept entirely on the client.

Slide 8

Slide 8 text

Cacheable Cache constraints require that the data within a response to a request be implicitly or explicitly labeled as cacheable or non-cacheable. If a response is cacheable, then a client cache is given the right to reuse that response data for later, equivalent requests.

Slide 9

Slide 9 text

Layered System The layered system style allows an architecture to be composed of hierarchical layers by constraining component behavior such that each component cannot "see" beyond the immediate layer with which they are interacting.

Slide 10

Slide 10 text

Uniform Interface In order to obtain a uniform interface, multiple architectural constraints are needed to guide the behavior of components. REST is defined by four interface constraints: identification of resources; manipulation of resources through representations; self-descriptive messages; and, hypermedia as the engine of application state.

Slide 11

Slide 11 text

Uniform Interface • Defines the interface between client and server • Fundamental for RESTful design • HTTP is the most famous REST architectural style. We are all used to: • HTTP verbs • URIs (uniform ressource identifier) • HTTP response (status, body)

Slide 12

Slide 12 text

WWW is the largest implementation conforming to the REST architectural style

Slide 13

Slide 13 text

On the WWW, Hypermedia controls allow you to surf the web without any issues when someone changes its landing page. What about APIs?

Slide 14

Slide 14 text

REST APIs • REST APIs follows the principle of Uniform Interface • they are resource-based: we’re talking about « things », not like in SOAP/RPC where we’re dealing with « actions » • resources are identified by URIs (multiple URIs can map to the same resource) • resources are NOT their representation • But they fail to be hypermedia

Slide 15

Slide 15 text

Example Request on the twitter API GET https://api.twitter.com/1.1/friends/ids.json?cursor=-1&screen_name=twitterapi&count=10 {      "previous_cursor":  0,      "ids":  [          657693,          183709371,          7588892,          38895958,          22891211,          9019482,          14488353,          11750202,          12249,          9160152,      ],      "previous_cursor_str":  "0",      "next_cursor":  0,      "next_cursor_str":  "0"   } What is the appropriate URL? How can I fetch the user info?

Slide 16

Slide 16 text

• when you wrote a client for an APIs, you write assumptions (based on documentation) on resources locations • your client will be broken at the very moment the location has moved • strong coupling between client and server

Slide 17

Slide 17 text

No content

Slide 18

Slide 18 text

2 - HATEOAS Hypermedia As The Engine Of Application State

Slide 19

Slide 19 text

• Clients must make state transitions only through actions that are dynamically identified within hypermedia by the server (e.g., by hyperlinks within hypertext). • Except for simple fixed entry points to the application, a client does not assume that any particular action is available for any particular resources beyond those described in representations previously received from the server (in other words: a client does not require any prior knowledge to interact with any kind of application) • ideally, client is valid forever, just as browsers work (note the “ideally”)

Slide 20

Slide 20 text

The Hypermedia ZOO • Lots of format have been issued to try to solve the hypermedia approach for APIs • Mime Types: HAL, Siren, JSON-LD, Atom+Pub, Collection+JSON • Description of available links/resources: RDF/ ALPS… • Much of the thinking around these hypermedia formats focuses on the way messages are designed.

Slide 21

Slide 21 text

AtomPub My New Collection urn:uuid:de46e3a1-e489-41a6-88a6-21e7f0e8e2d8 2009-06-12T12:13:46Z Daffy used to tell the client of this API where to edit this particular resource

Slide 22

Slide 22 text

Collection+JSON { “collection”: { “version”: “1.0”, “href”: “http://example.org/friends”, “items”: [ “href”: “http://example.org/friends/kevin”, “data”: [ {“name”: “full-name”, “value”: “Kevin Swiber” } ] ], “queries”: [ {“rel”: “search”, “href”: “./search”, “data”: [ {“name”: “search”, “value”: “” } ] } } URI of the collection Collection’s elements How to query collection’s elements

Slide 23

Slide 23 text

The Semantic Zoo • each hypermedia control is associated to a link “relation” • IANA defines a set of basic links relations (http:// www.iana.org/assignments/link-relations/link- relations.xhtml): about, next, edit, payment, related, up, version-history … • Sadly, you will need your own links definition.

Slide 24

Slide 24 text

Moving to Hypermedia {      "previous_cursor":  0,      "ids":  [          657693,          183709371,          7588892,          38895958,          22891211,          9019482,          14488353,          11750202,          12249,          9160152,      ],      "previous_cursor_str":  "0",      "next_cursor":  0,      "next_cursor_str":  "0"   } Not a link! Not a valid link relation

Slide 25

Slide 25 text

Moving to Hypermedia {      "prev":  https://api.twitter.com/1.1/friends/ids.json? screen_name=twitterapi&count=10&cursor=0,      "ids":  [          657693,          183709371,          7588892,          38895958,          22891211,          9019482,          14488353,          11750202,          12249,          9160152,      ],      "previous_cursor_str":  "0",      "next_cursor":  0,      "next_cursor_str":  "0"   }

Slide 26

Slide 26 text

Moving to Hypermedia {      "prev":  https://api.twitter.com/1.1/friends/ids.json? cursor=-­‐1&screen_name=twitterapi&count=10&cursor=0,      "ids":  [          657693,          183709371,          7588892,          38895958,          22891211,          9019482,          14488353,          11750202,          12249,          9160152,      ],      "previous_cursor_str":  "0",      "next_cursor":  0,      "next_cursor_str":  "0"   } Not a link! Not a valid link relation

Slide 27

Slide 27 text

Moving to Hypermedia {      "prev":  https://api.twitter.com/1.1/friends/ids.json? cursor=-­‐1&screen_name=twitterapi&count=10&cursor=0,      "ids":  [          657693,          183709371,          7588892,          38895958,          22891211,          9019482,          14488353,          11750202,          12249,          9160152,      ],      "previous_cursor_str":  "0",      "next":  https://api.twitter.com/1.1/friends/ids.json? cursor=-­‐1&screen_name=twitterapi&count=10&cursor=0,      "next_cursor_str":  "0"   } Not a link! Not a valid link relation

Slide 28

Slide 28 text

Moving to Hypermedia {      "prev":  https://api.twitter.com/1.1/friends/ids.json? cursor=-­‐1&screen_name=twitterapi&count=10&cursor=0,      "ids":  [          657693,          183709371,          7588892,          38895958,          22891211,          9019482,          14488353,          11750202,          12249,          9160152,      ],      "previous_cursor_str":  "0",      "next":  https://api.twitter.com/1.1/friends/ids.json? cursor=-­‐1&screen_name=twitterapi&count=10&cursor=0,      "next_cursor_str":  "0"   } How to fetch these items?

Slide 29

Slide 29 text

Moving to Hypermedia {      "prev":  https://api.twitter.com/1.1/friends/ids.json? cursor=-­‐1&screen_name=twitterapi&count=10&cursor=0,      "ids":  [          {“657693”:  {“user_lookup”:  “https://api.twitter.com/1.1/ users/lookup.json?user_id=657693”,  “friends”:  “https:// api.twitter.com/1.1/friends/ids.json?user_id=657693”},  …      ],      "previous_cursor_str":  "0",      "next":  https://api.twitter.com/1.1/friends/ids.json? cursor=-­‐1&screen_name=twitterapi&count=10&cursor=0,      "next_cursor_str":  "0"   }

Slide 30

Slide 30 text

Moving to Hypermedia {      "prev":  https://api.twitter.com/1.1/friends/ids.json? cursor=-­‐1&screen_name=twitterapi&count=10&cursor=0,      "ids":  [          {“657693”:  {“user_lookup”:  “https://api.twitter.com/1.1/ users/lookup.json?user_id=657693”,  “friends”:  “https:// api.twitter.com/1.1/friends/ids.json?user_id=657693”},  …      ],      "previous_cursor_str":  "0",      "next":  https://api.twitter.com/1.1/friends/ids.json? cursor=-­‐1&screen_name=twitterapi&count=10&cursor=0,      "next_cursor_str":  "0"   } Totally Unknown Format!

Slide 31

Slide 31 text

Moving to Hypermedia { “collection” : { “version” : “1.0”, “href” : “/friends/ids.json?cursor=-1&screen_name=twitterapi&count=10”, “links” : [ {“rel” : “next”, “href” : “/friends/ids.json? screen_name=twitterapi&count=5000&cursor=0”}, {“rel” : “prev”, “href” : “/friends/ids.json?screen_name=twitterapi&count=5000&cursor=0”} ], “items” : [ { “href” : “/users/lookup.json?user_id=657693”, “data” : [ {“name” : “id”, “value” : “657693”, “prompt” : “User Id”}, ], “links” : [ {“rel” : “friends”, “href” : “/friends/ids.json?user_id=657693”, “prompt” : “Friends”} ] } ] }

Slide 32

Slide 32 text

COLLECTION+JSON { "collection" : { "version" : "1.0", "href" : "http://example.org/friends/", "links" : [], "items" : [], "queries" : [], "template" : { "data" : [] } } } Items of the collection How to query the collection How to create an item in my collection URL of this collection Relation to linked resources

Slide 33

Slide 33 text

COLLECTION+JSON { "collection" : { "version" : "1.0", "href" : "http://example.org/friends/", "links" : [{"rel" : "feed", "href" : "http://example.org/friends/rss"}], }] … } } Relations

Slide 34

Slide 34 text

COLLECTION+JSON { "collection" : { "version" : "1.0", "href" : "http://example.org/friends/", "links" : [], "items" : [{ "href" : "http://example.org/friends/msmith", "data" : [ {"name" : "full-name", "value" : "M. Smith", "prompt" : "Full Name"}, {"name" : "email", "value" : "[email protected]", "prompt" : "Email"} ], "links" : [ {"rel" : "blog", "href" : "http://example.org/blogs/msmith", "prompt" : "Blog"}, {"rel" : "avatar", "href" : "http://example.org/images/msmith", "prompt" : "Avatar", "render" : "image"} ] }] … } } Items of the collection

Slide 35

Slide 35 text

COLLECTION+JSON { "collection" : { "version" : "1.0", "href" : "http://example.org/friends/", "links" : [], "items" : [], "queries" : [ {"rel" : "search", "href" : “http://example.org/friends/search", "prompt" : "Search", "data" : [{"name" : "search", "value" : ""}] } ], … } } How to query the collection

Slide 36

Slide 36 text

COLLECTION+JSON { "collection" : { "version" : "1.0", "href" : "http://example.org/friends/", "links" : [], "items" : [], "queries" : [], "template" : { "data" : [ {"name" : "full-name", "value" : "", "prompt" : "Full Name"}, {"name" : "email", "value" : "", "prompt" : "Email"}, {"name" : "blog", "value" : "", "prompt" : "Blog"}, {"name" : "avatar", "value" : "", "prompt" : "Avatar"} ] } } } How to create an item in my collection

Slide 37

Slide 37 text

3- Build Adaptable APIs

Slide 38

Slide 38 text

No content

Slide 39

Slide 39 text

• designing a 100% hypermedia compliant API is hard (if not impossible) • moreover, there’s only a few hypermedia clients • is there any advantages to go hypermedia? YES!

Slide 40

Slide 40 text

• design unicity of request/replies over your entire API • thanks to “links” attributes, easy to create smart clients • not relying on hardcoded links = lower client maintenance

Slide 41

Slide 41 text

Example: a simple HAL CLI See: https://gist.github.com/ohe/ccf6b117b76cba9b77af BASE="http://haltalk.herokuapp.com" class HALClient(object): @staticmethod def request(endpoint): return request("GET", BASE + endpoint).json() or {} def __init__(self, endpoint="/"): response = self.request(endpoint) for key in response.get('_links', {}).keys(): if key.startswith("ht:") : if isinstance(response['_links'][key], dict): with ignored(ValueError): setattr(self, key[3:], partial(HALClient, response['_links'][key]['href'])) if isinstance(response['_links'][key], list): for each in response['_links'][key]: with ignored(): title = each["title"] or "NONE" if isinstance(title, basestring): key = slugify(title, separator="_") if len(key) < 50: setattr(self, key, partial(HALClient, each['href']))

Slide 42

Slide 42 text

Hypermedia at dddddd • built with Flask on top of a classic MVC model • Where Hypermedia should be inserted? • the model: hypermedia controllers are independent from the models • view: “irrelevant” for APIS • controller! • based on Collection+JSON

Slide 43

Slide 43 text

Example @mod.route('/', methods=['GET']) @hypermediafy(Domain) def get(domain): domain = Domain.get(name=domain) return domain, 200

Slide 44

Slide 44 text

Example class DomainHypermedia(HyperModel): cls_model = Domain blueprint_name = 'domains' content = 'domain' def links(self): links = [] if len(self.models) == 1: model = self.models[0] links.append({'rel': 'connectors', 'name': 'connectors', 'href': url_for('connectors.index', domain=model.name)}) links.append({'rel': 'campaigns', 'name': 'campaigns', 'href': url_for('campaigns.index', domain=model.name)}) links.append({'rel': 'streams', 'name': 'streams', 'href': url_for('streams.index', domain=model.name)}) return links Default Hypermedia Controls

Slide 45

Slide 45 text

No content

Slide 46

Slide 46 text

Thank You!