42
{"links":[ {"rel":"author", "uri":"http://javier-ramirez.com"}, {"rel":"work", "uri":"http://aspgems.com"}, {"rel":"blog", "uri":"http://formatinternet.com"}, {"rel":"twittEr", "uri":"http//twitter.com/supercoco9 "} ]} usable REST APIs

Usable Rest APIs by Javier Ramirez at London Ruby User Group

Embed Size (px)

DESCRIPTION

With the adoption of REST, the proliferation of smartphones and tablets, and the second coming of JavaScript, exposing our applications as a service is now more important than ever. Rails or Sinatra make really easy to create a (kinda) RESTful API but, in many occassions, these APIs are designed without really thinking on the developers that will have to use them. I want to talk about some of the points that can help making your API more developer-friendly. Some of the areas I’ll cover will be discoverability, authentication, headers, formats, parameters, documentation and tools. Talk delivered at London Ruby User Group on 12/12/2011

Citation preview

Page 1: Usable Rest APIs by Javier Ramirez at London Ruby User Group

{"links":[ {"rel":"author", "uri":"http://javier-ramirez.com"}, {"rel":"work", "uri":"http://aspgems.com"}, {"rel":"blog", "uri":"http://formatinternet.com"}, {"rel":"twittEr", "uri":"http//twitter.com/supercoco9"}]}

usable REST APIs

Page 2: Usable Rest APIs by Javier Ramirez at London Ruby User Group

1996

Page 3: Usable Rest APIs by Javier Ramirez at London Ruby User Group

1995

Page 4: Usable Rest APIs by Javier Ramirez at London Ruby User Group

1996

Page 5: Usable Rest APIs by Javier Ramirez at London Ruby User Group

1994

Page 6: Usable Rest APIs by Javier Ramirez at London Ruby User Group

2001

Page 7: Usable Rest APIs by Javier Ramirez at London Ruby User Group

1999

Page 8: Usable Rest APIs by Javier Ramirez at London Ruby User Group

2004

Page 9: Usable Rest APIs by Javier Ramirez at London Ruby User Group

Web usability is an approach to make web sites

easy to use for an end-user, without the requirement that any specialized training be

undertaken.[]

Page 10: Usable Rest APIs by Javier Ramirez at London Ruby User Group

LearnabilityEfficIeNcyMemorabiliTyErrorsSatisfActiOn

Page 11: Usable Rest APIs by Javier Ramirez at London Ruby User Group

I want YOUto make

a (REST) API

Page 12: Usable Rest APIs by Javier Ramirez at London Ruby User Group

REST in a nutshell

client server stateless layered and cacheable

Resources

Resource Identifiers

Resource metadata

Uniform interface

operations

Representations

Representation metadata

Optionally: code on demand

Page 13: Usable Rest APIs by Javier Ramirez at London Ruby User Group

easy

making a REST API

with Rails?

Page 14: Usable Rest APIs by Javier Ramirez at London Ruby User Group

BASIC

WEB/api

functionality

IN RAILS

Page 15: Usable Rest APIs by Javier Ramirez at London Ruby User Group

Cohesion

pleasE

Page 16: Usable Rest APIs by Javier Ramirez at London Ruby User Group

separation of concerns

Page 17: Usable Rest APIs by Javier Ramirez at London Ruby User Group

SUCCESS consistently

fail consistently

Page 18: Usable Rest APIs by Javier Ramirez at London Ruby User Group

expose ONLY

WHAT ISStrictly

necessary

Page 19: Usable Rest APIs by Javier Ramirez at London Ruby User Group

resources are not models

Page 20: Usable Rest APIs by Javier Ramirez at London Ruby User Group

Aggregation/

composition

Multiple

representations

Page 21: Usable Rest APIs by Javier Ramirez at London Ruby User Group

Multiple consumers

Page 22: Usable Rest APIs by Javier Ramirez at London Ruby User Group

All your

FORMAT

are belong

to us

Page 23: Usable Rest APIs by Javier Ramirez at London Ruby User Group

> curl -d "[email protected]&password=keepdreaming" https://invoicefu.com/api/session?format=json

{"user":{"id":108,"name":"Nicolas Carroll","email":"[email protected]","locale":"en","twitter_nickname":null,"facebook_uid":null,"facebook_nickname":null,"api_key":"dbd349b30b6d9fde97b01b827e6be5ed1e4fbe72","links":[{"rel":"session","uri":"https://invoicefu.com/api/session","methods":"GET,POST,DESTROY"},"rel":"account","uri":"https://invoicefu.com/api/accounts/108-cole-mertz-fake","methods":"GET,PUT"},"rel":"clients","uri":"https://invoicefu.com/api/accounts/108-cole-mertz-fake/clients","methods":"GET,POST"},{"rel":"new_client","uri":"https://invoicefu.com/api/accounts/108-cole-mertz-fake/clients/new","methods":"GET"},{"rel":"invoices","uri":"https://invoicefu.com/api/accounts/108-cole-mertz-fake/invoices","methods":"GET,POST"},{"rel":"new_invoice","uri":"https://invoicefu.com/api/accounts/108-cole-mertz-fake/invoices/new","methods":"GET"},{"rel":"proformas","uri":"https://invoicefu.com/api/accounts/108-cole-mertz-fake/proformas","methods":"GET,POST"},{"rel":"new_proforma","uri":"https://invoicefu.com/api/accounts/108-cole-mertz-fake/proformas/new","methods":"GET"}]}}

Page 24: Usable Rest APIs by Javier Ramirez at London Ruby User Group

> curl -d "[email protected]&password=yeahyeah" "https://invoicefu.com/api/session?&format=xml

<?xml version="1.0" encoding="UTF-8"?><user> <id>108</id> <name>Nicolas Carroll</name> <email>[email protected]</email> <locale>en</locale> <twitter-nickname nil="true"></twitter-nickname> <facebook-uid nil="true"></facebook-uid> <facebook-nickname nil="true"></facebook-nickname> <api-key>dbd349b30b6d9fde97b01b827e6be5ed1e4fbe72</api-key> <links> <link> <rel>session</rel> <uri>https://invoicefu-localhost.com/api/session</uri> <methods>GET,POST,DESTROY</methods> </link> <link> <rel>account</rel> <uri>https://invoicefu-localhost.com/api/accounts/108-cole-mertz-fake</uri> <methods>GET,PUT</methods> </link> <link> <rel>clients</rel> <uri>https://invoicefu-localhost.com/api/accounts/108-cole-mertz-fake/clients</uri> <methods>GET,POST</methods> </link> <link> <rel>new_client</rel> <uri>https://invoicefu-localhost.com/api/accounts/108-cole-mertz-fake/clients/new</uri> <methods>GET</methods> </link> (…)

</links></user>

can I haz cat readable anzwa

Page 25: Usable Rest APIs by Javier Ramirez at London Ruby User Group

THE

ACCEPT

HEADER

HTTP/REST StandardUnambiguousResources != RepresentationsVersion as you need it

Not everyone supports headers or custom types

Less obviousHarder to useNon standard content-typesSkips HTTP server logs

Accept: application/vnd.aspgems.invoicefu.v1.xml

Page 26: Usable Rest APIs by Javier Ramirez at London Ruby User Group
Page 27: Usable Rest APIs by Javier Ramirez at London Ruby User Group
Page 28: Usable Rest APIs by Javier Ramirez at London Ruby User Group

templates

for new

resources> curl "https://invoicefu.com/api/v1/accounts/108-cole-mertz-fake/invoices/new?api_key=ddd349b30b6d9fde97b01b827e6be5ed1e4fbe72&format=json"

{"invoice":{"number":"2011/30","issued_on":"2011-12-12","proforma_id":null,"notes":null,"footer":null,"locale":"en","currency_code":"USD","currency_symbol":"$","ac_name":"Cole-Mertz#FAKE","ac_company_number_name":"Company number","ac_company_number":"25465828K","ac_tax_number_name":"VAT Number","ac_tax_number":"ES25464828k","ac_address":"234 brecknock road","ac_city":"london","ac_province":null,"ac_postal_code":"n18 5bq","ac_country_name":"United Kingdom","cl_email":null,"cl_name":null,"cl_company_number_name":null,"cl_company_number":null,"cl_tax_number_name":null,"cl_tax_number":null,"cl_address":null,"cl_city":null,"cl_province":null,"cl_postal_code":null,"cl_country_name":null,"invoice_lines":[],"discount_percent":null,"tax_lines":[{"name":"TVA","signed_percent":"19.6"}],"paid":"0.0","links":[{"rel":"payments","uri":"https://invoicefu.com/api/accounts/108-cole-mertz-fake/invoices//payments","methods":"POST"},{"rel":"account","uri":"https://invoicefu.com/api/accounts/108-cole-mertz-fake","methods":"GET,PUT"},{"rel":"client","uri":null,"methods":"GET,PUT,DELETE"},{"rel":"proforma","uri":null,"methods":"GET,PUT,DELETE"},{"rel":"pdf","uri":null,"methods":"GET"},{"rel":"invoices","uri":"https://invoicefu.com/api/accounts/108-cole-mertz-fake/invoices","methods":"GET,POST"}]}}j

Page 29: Usable Rest APIs by Javier Ramirez at London Ruby User Group

EASy

To

FIND

Page 30: Usable Rest APIs by Javier Ramirez at London Ruby User Group

> curl https://invoicefu.com?format=xml (or curl -H "Accept: application/xml" "https://invoicefu.com")

<?xml version="1.0" encoding="UTF-8"?><invoicefu> <links> <link> <rel>session</rel> <uri>https://invoicefu.com/api/session</uri> <methods>POST.DELETE</methods> </link> <link> <rel>countries</rel> <uri>https://invoicefu.com/api/countries</uri> <methods>GET</methods> </link> <link> <rel>api_v1</rel> <uri>https://invoicefu.com/api/session?api_version=1</uri> <methods>POST.DELETE</methods> </link> <link> <rel>xml_representation</rel> <uri>https://invoicefu.com/api/session?format=xml</uri> <methods>POST.DELETE</methods> </link> <link> <rel>json_representation</rel> <uri>https://invoicefu.com/api/session?format=json</uri> <methods>POST.DELETE</methods> </link> <link> <rel>strict_parameters</rel> <uri>https://invoicefu.com/api/session?strict=true</uri> <methods>POST.DELETE</methods> </link> </links></invoicefu>

Page 31: Usable Rest APIs by Javier Ramirez at London Ruby User Group

> curl -d "[email protected]&password=yeahyeah" "https://invoicefu.com/api/session?&format=xml

<?xml version="1.0" encoding="UTF-8"?><user> <id>108</id> <name>Nicolas Carroll</name> <email>[email protected]</email> <locale>en</locale> <twitter-nickname nil="true"></twitter-nickname> <facebook-uid nil="true"></facebook-uid> <facebook-nickname nil="true"></facebook-nickname> <api-key>dbd349b30b6d9fde97b01b827e6be5ed1e4fbe72</api-key> <links> <link> <rel>session</rel> <uri>https://invoicefu-localhost.com/api/session</uri> <methods>GET,POST,DESTROY</methods> </link> <link> <rel>account</rel> <uri>https://invoicefu-localhost.com/api/accounts/108-cole-mertz-fake</uri> <methods>GET,PUT</methods> </link> <link> <rel>clients</rel> <uri>https://invoicefu-localhost.com/api/accounts/108-cole-mertz-fake/clients</uri> <methods>GET,POST</methods> </link> <link> <rel>new_client</rel> <uri>https://invoicefu-localhost.com/api/accounts/108-cole-mertz-fake/clients/new</uri> <methods>GET</methods> </link> <link> <rel>invoices</rel> <uri>https://invoicefu-localhost.com/api/accounts/108-cole-mertz-fake/invoices</uri> <methods>GET,POST</methods> </link>

(…)

</links></user>

Page 32: Usable Rest APIs by Javier Ramirez at London Ruby User Group

BASIC ACCESS AUTHENTICATION

OAUTH

TOKEN

authenticate_or_request_with_http_basic do |login, password| User.find_by_login_and_password login, password endUser and password must be passed every time

Digest::SHA1.hexdigest( Time.now.to_s.split(//).sort_by {rand}.join ) User.find_by_login_and_api_key( params[:login], params[:api_key] ) Client can send it as a parameter or as a header

Depends on third party libraries Requires initial registration of client and more integration

Page 33: Usable Rest APIs by Javier Ramirez at London Ruby User Group

Performance

Page 34: Usable Rest APIs by Javier Ramirez at London Ruby User Group

params &

debug

Page 35: Usable Rest APIs by Javier Ramirez at London Ruby User Group

> curl "https://invoicefu.com/api/accounts/108-cole-mertz-fake/invoices/new?api_key=ddd349b30b6d9fde97b01b827e6be5ed1e4fbe72&format=xml&debug=1"

<?xml version="1.0" encoding="UTF-8"?><errors> <error>extra params found: debug. Allowed params are: account_id,client_id,invoice_id,proforma_id</error></errors>

> curl "https://invoicefu.com/api/accounts/108-cole-mertz-fake/invoices/new?api_key=ddd349b30b6d9fde97b01b827e6be5ed1e4fbe72&format=xml&debug=1&strict=false"<?xml version="1.0" encoding="UTF-8"?><invoice> <number>2011/30</number> <issued-on>2011-12-11</issued-on> <proforma-id nil="true"></proforma-id> (...) <links> (...) <link> <rel>invoices</rel> <uri>https://invoicefu-localhost.com/api/accounts/108-cole-mertz-fake/invoices</uri> <methods>GET,POST</methods> </link> </links></invoice>

Page 36: Usable Rest APIs by Javier Ramirez at London Ruby User Group
Page 37: Usable Rest APIs by Javier Ramirez at London Ruby User Group

WADLjson schema

helping

your users

Page 38: Usable Rest APIs by Javier Ramirez at London Ruby User Group

{ "name":"Product", "properties":{ "id":{ "type":"number", "description":"Product identifier", "required":true }, "name":{ "description":"Name of the product", "type":"string", "required":true }, "price":{ "required":true, "type": "number", "minimum":0, "required":true }, "tags":{ "type":"array", "items":{ "type":"string" } } }, "links":[ { "rel":"full", "href":"{id}" }, { "rel":"comments", "href":"comments/?id={id}" } ] }

<?xml version="1.0"?> <application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://wadl.dev.java.net/2009/02 wadl.xsd" xmlns:tns="urn:yahoo:yn" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:yn="urn:yahoo:yn" xmlns:ya="urn:yahoo:api" xmlns="http://wadl.dev.java.net/2009/02"> <grammars> <include href="NewsSearchResponse.xsd"/> <include href="Error.xsd"/> </grammars> <resources base="http://api.search.yahoo.com/NewsSearchService/V1/"> <resource path="newsSearch"> <method name="GET" id="search"> <request> <param name="appid" type="xsd:string" style="query" required="true"/> 22 <param name="type" style="query" default="all"> <option value="all"/> <option value="any"/> <option value="phrase"/> </param> <param name="start" style="query" type="xsd:int" default="1"/> <param name="language" style="query" type="xsd:string"/> </request> <response status="200"> <representation mediaType="application/xml" element="yn:ResultSet"/> </response> <response status="400"> <representation mediaType="application/xml" element="ya:Error"/> </response> </method> </resource> </resources> </application>

Page 39: Usable Rest APIs by Javier Ramirez at London Ruby User Group

tools

curl

Hurl

Httparty

restclient

Page 40: Usable Rest APIs by Javier Ramirez at London Ruby User Group

apigee

Page 41: Usable Rest APIs by Javier Ramirez at London Ruby User Group
Page 42: Usable Rest APIs by Javier Ramirez at London Ruby User Group