api design - 3rd edition

Post on 26-Jan-2015

151 Views

Category:

Technology

3 Downloads

Preview:

Click to see full reader

DESCRIPTION

 

TRANSCRIPT

API Design3rd Edition

Kevin Swiber@kevinswiber

Apigee@apigee

Brian Mulloy@landlessness

groups.google.com/group/api-craft

youtube.com/apigee

slideshare.net/apigee

@landlessness @kevinswiber

“ The real issue is about design: designing things that have the power required for the job while maintaining understandability, the feeling of control, and the pleasure of accomplishment.

-Donald Norman

http://www.flickr.com/photos/mattharvey1/5712604622/

Agenda

• Recap Previous Edition• API Modeling• Security• Message Design• Hypermedia• Transactions

http://offers.apigee.com/web-api-design-ebook/

URL DesignPlural nouns for collections

/dogs

ID for entity /dogs/1234

Associations /owners/5678/dogs

4 HTTP Methods

POST GET PUT DELETE

Bias toward concrete names

/dogs (not animals)

Multiple formats in URL

/dogs.json/dogs.xml

Paginate with limit and offset

?limit=10&offset=0

Query params ?color=red&state=running

Partial selection ?fields=name,state

Use medial capitalization

"createdAt": 1320296464myObject.createdAt;

Use verbs for non-resource requests

/convert?from=EUR&to=CNY&amount=100

Search /search?q=happy%2Blabrador

DNS api.foo.comdevelopers.foo.com

Errors8 Status Codes 200 201 304 400 401 403 404

500

Verbose messages {"msg": "verbose, plain language hints"}

VersioningInclude version in URL

/v1/dogs

Keep one previous version long enough for developers to migrate

/v1/dogs/v2/dogs

Client ConsiderationsClient does not support HTTP status codes

?suppress_response_codes=true

Client does not support HTTP methods

GET /dogs?method=postGET /dogsGET /dogs?method=putGET /dogs?method=delete

Complement API with SDK and code libraries

1. JavaScript2. …3. …

How do we get started with our API?

Build an API Model

http://www.flickr.com/photos/brent_nashville/2156695472/in/photostream/

Don’t Go Cowboy

http://www.flickr.com/photos/theory/3364213389/in/photostream/

How do we secure our API?

Authorization: Basic aWhlYXJ0OmFwaXM=

Twitter Streaming API

Authorization: AWS AKIAIOSFODNN7EXAMPLE:frJIUNo//yllqDzg=

Amazon Web Services API

Authorization: Bearer 1/fFBGRNJru1FQd44AzqT3Zg

Google API

Authorization: Bearer 1/fFBGRNJru1FQd44AzqT3Zg

OAuth2

How do approach message design?

Support multiple formats JSON and XML

Make JSON the default

How do we represent single items?

21

Twitter Foursquare{ "meta": {…}, "data": {}}

Instagram{ "meta": {…}, "notifications": […], "response": {}}

{ "created_at": "Thu Jan 10 08:44:59 +0000 2013", "id": 289291736440791040, "id_str": "289291736440791040", "text": "@landlessness here's one for you: 50-year plan to fix Detroit\n\nhttp://t.co/kJ2l1FZv", "source": "<a href="http://twitter.com/download/android" rel="nofollow">Twitter for Android</a>", "truncated": false, "in_reply_to_status_id": null, "in_reply_to_status_id_str": null, "in_reply_to_user_id": 41020312, "in_reply_to_user_id_str": "41020312", "in_reply_to_screen_name": "landlessness", "user": {…}, "geo": {…}, "coordinates": {…}, "place": {…}, "contributors”:{…}, "retweet_count": 0, "entities": {…}, "favorited": false, "retweeted": false, "possibly_sensitive": false}

22

Twitter Foursquare{ "meta": {…}, "data": { "attribution": {…}, "type": "image", "location": {…}, "comments": {…}, "filter": "Sierra", "created_time": "1357826573", "link": "http://instagr.am/p/UTk5Xut3gN/", "likes": {…}, "images": {…}, "caption": {…}, "user_has_liked": false, "id": "365798266911553549_3573549", "user": {…} }}

Instagram{ "meta": {…}, "notifications": […], "response": { "checkin": { "id": "50eeff78e4b0f8e9624ea5f8", "createdAt": 1357840248, "type": "checkin", "shout": "Pharmacy #DRUGS!!! #ToothPulled :(", "timeZone": "America/Detroit", "timeZoneOffset": -300, "user": {…}, "venue": {…}, "source": {…} } }}

{ "created_at": "Thu Jan 10 08:44:59 +0000 2013", "id": 289291736440791040, "id_str": "289291736440791040", "text": "@landlessness here's one for you: 50-year plan to fix Detroit\n\nhttp://t.co/kJ2l1FZv", "source": "<a href="http://twitter.com/download/android" rel="nofollow">Twitter for Android</a>", "truncated": false, "in_reply_to_status_id": null, "in_reply_to_status_id_str": null, "in_reply_to_user_id": 41020312, "in_reply_to_user_id_str": "41020312", "in_reply_to_screen_name": "landlessness", "user": {…}, "geo": {…}, "coordinates": {…}, "place": {…}, "contributors”:{…}, "retweet_count": 0, "entities": {…}, "favorited": false, "retweeted": false, "possibly_sensitive": false}

23

Take the best of Foursquare and Instagram

{ "meta": {…}, "dog": {…} "notifications": […],}

How do we represent collections?

25

Twitter Foursquare{ "meta": {…}, "data": [ { "attribution": {…}, "type": "image", "location": {…}, "comments": {…}, "filter": "Sierra", "created_time": "1357826573", "link": "http://instagr.am/p/UTk5Xut3gN/", "likes": {…}, "images": {…}, "caption": {…}, "user_has_liked": false, "id": "365798266911553549_3573549", "user": {…} }, {…}, {…} ] }}

Instagram{ "meta": {…}, "notifications": […], "response": { "recent": [ { "id": "50eeff78e4b0f8e9624ea5f8", "createdAt": 1357840248, "type": "checkin", "shout": "Pharmacy #DRUGS!!! #ToothPulled :(", "timeZone": "America/Detroit", "timeZoneOffset": -300, "user": {…}, "venue": {…} }, {…}, {…}, ] }}

[ { "created_at": "Thu Jan 10 08:44:59 +0000 2013", "id": 289291736440791040, "id_str": "289291736440791040", "text": "@landlessness here's one for you: 50-year plan to fix Detroit\n\nhttp://t.co/kJ2l1FZv", "source": "<a href="http://twitter.com/download/android" rel="nofollow">Twitter for Android</a>", "truncated": false, "in_reply_to_status_id": null, "in_reply_to_status_id_str": null, "in_reply_to_user_id": 41020312, "in_reply_to_user_id_str": "41020312", "in_reply_to_screen_name": "landlessness", "user": {…}, "geo": {…}, "coordinates": {…}, "place": {…}, "contributors”:{…}, "retweet_count": 0, "entities": {…}, "favorited": false, "retweeted": false, "possibly_sensitive": false }, {…}, {…}]

26

Take the best of Foursquare and Instagram

{ "meta": {…}, "dogs": {…} /* include same info as single */ "notifications": […],}

How do we represent search results?

“ Selecting results is not the same as searching.

-Facebook API

29

Bing Search Google Custom Search{"kind": "Listing", "data": { "after": "t3_qy342", "before": null, "children": [ { "data": { "id": "f605o", "num_comments": 943, "score": 1146, "ups": 3110, "downs": 1964, "created": 1295553753.0, "url": "http://www.reddit.com/r/AskReddit/comments/f605o/this_is_a_long_shot_any_sushi_chefs_need_a_job_in/", "author": "jining", } }, { "data": { "id": "c9eng”, "num_comments": 308, "score": 59, "ups": 128, "downs": 69, "created": 1275155900.0, "url": "http://www.reddit.com/r/IAmA/comments/c9eng/i_am_a_sushi_man_ama/","saved": false, "is_self": true, "permalink": "/r/IAmA/comments/c9eng/i_am_a_sushi_man_ama/", "author": "IAmASushiMan” } } ] }}

Reddit Search{ "kind": "customsearch#search", "url": { "type": "application/json", "template": "https://www.googleapis.com/customsearch/v1?q={searchTerms}…}, "queries": { "request": [ { "title": "Google Custom Search - sushi", "totalResults": "15000000", "searchTerms": "sushi", "count": 10, "startIndex": 1, } ] }, "context": { "title": "Custom Search" }, "searchInformation": { "searchTime": 0.314942, "formattedSearchTime": "0.31", "totalResults": "15000000", "formattedTotalResults": "15,000,000" }, "items": [ { "kind": "customsearch#result", "title": "Standardized Usage Statistics Harvesting Initiative (SUSHI ... - NISO", "htmlTitle": "\u003cb\u003eStandardized Usage Statistics Harvesting Initiative\u003c/b\u003e (\u003cb\u003eSUSHI\u003c/b\u003e \u003cb\u003e...\u003c/b\u003e - NISO", "link": "http://www.niso.org/workrooms/sushi", "displayLink": "www.niso.org", "snippet": "The Standardized Usage Statistics Harvesting Initiative (SUSHI) Protocol standard (ANSI/NISO Z39.93-2007) defines an automated request and response model ...”,

{ "SearchResponse": { "Version": "2.2", "Query": { "SearchTerms": "sushi" }, "Web": { "Total": 95200000, "Offset": 0, "Results": [ { "Title": "The Sushi FAQ - The ultimate guide to sushi and sashimi and how to ...", "Description": "What is sushi?..", "Url": "http://www.sushifaq.com/", "CacheUrl": "http://cc.bingj.com/cache.aspx?q=sushi&d=4855190808495712&w=yU8fJS-YPT-f4svREMW2xSa75OoBUAZR", "DisplayUrl": "www.sushifaq.com", "DateTime": "2013-01-08T15:12:00Z" }, { "Title": "What Is Sushi? - Sushi Guide - Eatsushi.com", "Description": "Eatsushi.com...", "Url": "http://www.eatsushi.com/whatsushi.asp", "CacheUrl": "http://cc.bingj.com/cache.aspx?q=sushi&d=5013249854931333&w=ihBzI9k9WbrnwxKcV3n8mOoV97M89K-b", "DisplayUrl": "www.eatsushi.com/whatsushi.asp", "DateTime": "2013-01-07T13:51:00Z" } ] } }}

30

(Mostly) Follow Google Custom Search

{ "meta": { "limit": 1, "offset": 10, "totalResults": 15000000, "query": "sushi", "searchTime": 0.314942 }, "results": [ {}, {}, {} ]}

How do we represent links?

<link href=“http://api-public.netflix.com/catalog/people/100637” rel=“http://schemas.netflix.com/catalog/person.actor” title="Elijah Wood”></link>

Netflix API

"organization": { "login": "octocat", "id": 1, "url": "https://api.github.com/users/octocat", "type": "Organization”}

GitHub API

<link href=“http://api-public.netflix.com/catalog/people/100637” rel=“http://schemas.netflix.com/catalog/person.actor” title="Elijah Wood”></link>

Follow Netflix and the Web Linking spec

How do we represent actions?

GitHub

”actions": [{ “name”: “edit-repo”, “method”: “PATCH”, “href”: “https://api.github.com/repos/kevinswiber/siren”, ”fields”: [ { “name”: “name”, “type”: “text” }, { “name”: “description”, “type”: “text” }}]

Form-based API

"actions": [{ "name": "edit-repo", “method”: “PATCH”, “href”: “https://api.github.com/repos/kevinswiber/siren”, ”fields”: [ { “name”: “name”, “type”: “text” }, { “name”: “description”, “type”: “text” }}]

Form-based API

How do we represent metadata?

<item type="photo” id="10289” server="2” views="47” faves="0” more="0">

Flickr API (inline)

{ "size": "225.4KB”, "rev": "35e97029684fe”, "bytes": 230783, "modified": "Tue, 19 Jul 2011 21:55:38 +0000”, "path": "/Getting_Started.pdf”, "is_dir": false, "icon": "page_white_acrobat”, "root": "dropbox”, "mime_type": "application/pdf”, "revision": 220823}

Dropbox API (/metadata)

39

Include a metadata in responses and consider a dedicated/meta resource

{ "meta": { "size": "225.4KB”, "rev": "35e97029684fe”, "bytes": 230783, "modified": "Tue, 19 Jul 2011 21:55:38 +0000”, "path": "/Getting_Started.pdf”, "is_dir": false, "icon": "page_white_acrobat”, "root": "dropbox”, "mime_type": "application/pdf”, "revision": 220823 }}

What can we learn from hypermedia types?

Atom/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>

XHTML<ul class=“search user-list”> <li class=“user”> <div class="avatar"> <a href="/users/@kevin"> <img class=”user-image" src=”/img/avatar.png" /> </a> </div> <div> <a href=“/users/@kevin” rel=“user messages”> <span class=“user-name”>@kevin</span> (<span class="user-text">@kevin</span>) </a> </div> </li></ul>

HAL

{ “currentlyProcessing”: 14 “shippedToday”: 20, “_links”: { “self”: { “href”: “/orders?page=2” }, “next”: { “href”: “/orders?page=3” }, “prev”: { “href”: “/orders?page=1” } }}

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”: “” } ] }}

Siren{ “class”: [“owner”, “vip”], “properties”: { “name”: “Kevin” }, “entities”: [ { “rel”: [“https://rels.x.io/dog”], “href”: “https://api.x.io/dogs/1” } ], “actions”: [ { “name”: “adopt”, “method”: “POST”, “href”: “https://api.x.io/owners/1/dogs”, “fields”: [ { “name”: “dog-name”, “type”: “text” } ] } ], “links”: [ { “rel”: [“self”], “href”: “https://api.x.io/owners/1” } ]}

How do we accept binary data?

multipart/form-data

Content-Type: multipart/form-data; boundary=AaB03x

--AaB03xContent-Disposition: form-data; name=“caption”

Cool picture of my cat.--AaB03xContent-Disposition: form-data; name=“photo”; filename=“catpajamas.jpg”Content-Type: image/jpegContent-Transfer-Encoding: binary

…contents of catpajamas.jpg…--AaB03x

Inline Base64 Encoding

POST /photos{ “caption”: “Cool picture of my cat.” “photo”: “RHVkZSwgbXkgY2F0IGhhcyB0aGUgYmVzdCBwYWphbWFzLg==”}

2-Step Process

POST /photos{ “caption”: “Cool picture of my cat.”}

PUT /photos/1234/dataContent-Type: image/jpegContent-Length: 240Content-Transfer-Encoding: binary

…binary content…

Opt for multipart/form-data.Be consistent.

How do we support caching?

Expiration

200 OKCache-Control: private, max-age=2592000

ETags

GET /dogs/1ETag: “a7D92kda94aisdfG”

GET /dogs/1If-None-Match: “a7D92kda94aisdfG”

Last-Modified

GET /dogs/1Last-Modified: Thu, 10 Jan 2013 19:43:31 GMT

GET /dogs/1If-Modified-Since: Thu, 10 Jan 2013 19:43:31 GMT

Think about the client.

Do we need a JavaScript API?

Yes. Follow LinkedIn’s lead.

What about posting data?

application/x-www-form-urlencoded

breed=Dachshund&name=Hotdog&age=2

application/xml

<dog> <breed>Dachshund</breed> <name>Hotdog</name> <age>2</age></dog>

application/json

{ “breed”: “Dachshund”, “name”: “Hotdog”, “age”: 2}

Favor application/x-www-form-urlencoded data.

How do we handle transactions?

Create a Transaction

POST /carts…201 CreatedLocation: /carts/1

Add ItemsPOST /carts/1/items/{ “productId”: “mittens123”, “quantity”: 1 }…201 CreatedLocation: /cartItems/1234

Commit the TransactionPOST /carts/1{ “message”: “checkout” }…200 OK

• Checkout previous editions for URI design• Start with API modeling• Use OAuth for security• Good message design is for developers• Learn from hypermedia specs• More on transactions later

Summary

Questions?

THANK YOUSubscribe to API webinars at:

youtube.com/apigee

THANK YOUQuestions and ideas to:

groups.google.com/group/api-craft

THANK YOUContact us at:

@landlessnessbrian@apigee.com

@kevinswiberkswiber@apigee.com

@apigee

top related