Upgrade to Pro — share decks privately, control downloads, hide ads and more …

APIs Hypermédia

APIs Hypermédia

Talk given at PyconFR - 2014/10/26

Olivier Hervieu

October 26, 2014
Tweet

More Decks by Olivier Hervieu

Other Decks in Programming

Transcript

  1. • 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)
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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.
  7. 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)
  8. On the WWW, Hypermedia controls allow you to surf the

    web without any issues when someone changes its landing page. What about APIs?
  9. 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
  10. 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?
  11. • 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
  12. • 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”)
  13. 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.
  14. AtomPub <?xml version="1.0"?> <entry xmlns="http://www.w3.org/2005/Atom"> <title>My New Collection</title> <id>urn:uuid:de46e3a1-e489-41a6-88a6-21e7f0e8e2d8</ id>

    <updated>2009-06-12T12:13:46Z</updated> <author> <name>Daffy</name> </author> <summary type="text" /> <content type="application/atom+xml;type=feed" src="http://example.org/my-new-collection"/> <link rel="edit" href="http://example.org/my-new-collection.atom" /> </entry> used to tell the client of this API where to edit this particular resource
  15. 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
  16. 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.
  17. 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
  18. 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"   }
  19. 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
  20. 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
  21. 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?
  22. 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"   }
  23. 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!
  24. 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”} ] } ] }
  25. 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
  26. COLLECTION+JSON { "collection" : { "version" : "1.0", "href" :

    "http://example.org/friends/", "links" : [{"rel" : "feed", "href" : "http://example.org/friends/rss"}], }] … } } Relations
  27. 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
  28. 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
  29. 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
  30. • 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!
  31. • 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
  32. 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']))
  33. 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
  34. 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