pox to hateoas: our company's journey building a hypermedia api
DESCRIPTION
We started FoxyCart.com in 2007 and soon after slapped together some XML and called it an API. As our customer base grew and third-party integrations emerged, the need for a true REST API became a priority. Beginning in 2012, we started researching best practices for modern API development. This talk will tell the story of that research and 10-months of development that followed. You'll get a look at the HAL hypermedia format along with some best practices we came up with for our problem domain of exposing our entire admin interface. We'll cover a lot. You may need a seat belt.TRANSCRIPT
From POX to HATEOASOur Company's Journey Building a Hypermedia API
Who...Luke StokesCo-Founder, Developer of [email protected]@lukestokeshttp://bestoked.blogspot.com
What...FoxyCart● ecommerce shopping cart system● Started by Brett Florio and myself in
2005/2006, incorporated in 2007.● SaaS (soon to be PaaS)● Built to integrate using your css/html (we're
not a CMS)● No duplication of data
Why...No duplication? Expose our data!POX: Plain Old XML● Confusing API actions
○ transaction_get, transaction_list, attribute_save, attribute_delete, transaction_modify, store_includes_get, etc
● Confusing request/response model● Tight coupling between the client and server
APIs and the Internet● Middleware ($$$)● RPC● SOAP● WSDL● Web Services (the WS-* stack)
Tight Coupling!
Does your browser do this?
REST to the rescueCRUD can be standardized via HTTP methods:
POST/PUT = createGET = readPATCH/PUT = updateDELETE = delete
(goodbye *_list, *_save, *_modify, etc methods)
REST to the rescueAgreed upon response codes● 1xx: Informational● 2xx: Success● 3xx: Redirection● 4xx: Client Error (You Screwed Up)● 5xx: Server Error (We Screwed Up)
http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
But... where do we start?
What's a perfect example of a REST API?
What is REST anyway?Six Constraints:● Client-server● Stateless● Cacheable● Layered system● Code on demand (optional)● Uniform interface
○ Identification of resources○ Manipulation of resources through these representations○ Self-descriptive messages○ Hypermedia as the engine of application state
REST Client Need-to-Know● Homepage● Hypermedia Format● Rel tags● Known media types (and possibly versions) ● Bonus stuff:
○ ?limit=5&offset=10○ ?order=<field> desc (or asc)○ ?fields=<field>,<field>,<field>○ ?<field>=<value>○ ?<field>=<some * partial value>
What's a media type?Examples:
application/jsonapplication/xmlapplication/hal+json
Originally defined as MIME types (RFC 2046)Also referred to as Content-Types
Platform = Will Not BreakEcommerce site broken at 4am and you changed nothing?
No one wants that phone call.
Flexible Versioning● FOXYCART-API-VERSION header
Flexible Versioning● FOXYCART-API-VERSION header● Per-resource vendor specific media type:
application/vnd.foxycart.com.store.v1+json
See: http://www.foxycart.com/blog/the-hypermedia-debate
Flexible Versioning● FOXYCART-API-VERSION header● Per-resource vendor specific media type:
application/vnd.foxycart.com.store.v1+json● Hypermedia allows us to version via the link
relation we code to.
Flexible Versioning● FOXYCART-API-VERSION header● Per-resource vendor specific media type:
application/vnd.foxycart.com.store.v1+json● Hypermedia allows us to version via the link
relation we code to.link: <https://example.com/users/2>;rel="https://example.com/rels/user"
Flexible Versioning● FOXYCART-API-VERSION header● Per-resource vendor specific media type:
application/vnd.foxycart.com.store.v1+json● Hypermedia allows us to version via the link
relation we code to.link: <https://example.com/users/2>;rel="https://example.com/rels/user"link: <https://example.com/customers/2>;rel="https://example.com/rels/customer"
Flexible VersioningHeader: FOXYCART-API-VERSION: 1Add "awesome_sauce" field:... "store_name":"My Store", "awesome_sauce":"pixie dust", "store_domain":"example",...Additions? No problem!
Flexible VersioningHeader: FOXYCART-API-VERSION: 1Remove "awesome_sauce" field...
Uh Oh.
Option 1: rel="https://example.com/store_v2"Option 2: FOXYCART-API-VERSION: 2
HEADERS: Array( [0] => Accept: application/hal+xml [1] => FOXYCART-API-VERSION: 1)
curl -X GET -H "Accept: application/hal+xml" -H "FOXYCART-API-VERSION: 1" https://api-sandbox.foxycart.com/
XML Accepts Header
Next...?<link rel="self" href="https://api-sandbox.foxycart.com/" title="Your API starting point."/><link rel="https://api.foxycart.com/rels/create_client" href="https://api-sandbox.foxycart.com/clients" title="Create a client via POST."/>
HATEOAS:Hypermedia as the Engine of Application State
Next...? OPTIONScurl -i -X OPTIONS
-H "Authorization: Bearer cae3c0c261fc71512428d612c1d2fd2a"-H "FOXYCART-API-VERSION: 1"-H "Accept: application/hal+xml"
"https://api-sandbox.foxycart.com/stores/2"
HTTP/1.1 200 OK..Allow: HEAD,GET,PUT,PATCH,DELETE...
Next...? POST: /clientsHEADERS: Array( [0] => Accept: application/hal+xml [1] => FOXYCART-API-VERSION: 1)
curl -X POST -H "Accept: application/hal+xml" -H "FOXYCART-API-VERSION: 1" https://api-sandbox.foxycart.com/clients
Error HandlingHTTP/1.1 400 Bad RequestDate: Fri, 30 Mar 2012 21:39:50 GMTConnection: closecache-control: private, must-revalidateContent-Type: application/vnd.error+xmlContent-Length: 546
https://github.com/blongden/vnd.error
Error Handling<errors xml:lang="en"> <error logref=42> <message>Validation failed</message> <link rel='help' href='http://...' title='Error information'/> <link rel='describes' href='http://...' title='Error description'/> </error></errors>
Let's take a look at the HAL Browser!
Hal Talk:http://haltalk.herokuapp.com/explorer/hal_browser.html#/
Foxy Cart:http://wiki.foxycart.com/v/0.0.0/hypermedia_apihttps://api-sandbox.foxycart.com/hal-browser/hal_browser.html#/https://api-sandbox.foxycart.com/hal-browser/
Examples!
What's all this token stuff?
* image credit: http://www.ibm.com/developerworks/library/x-androidfacebookapi/
OAuth 2.0 - Why Bother?Remember: Platform as a service!
● Hosted solutions● Hosted CMS● Self-hosted on a development platform
Simplify where we can:● If you created it, you get full access to it and
we can skip the OAuth Dance
Client Code$resp = $client->get(
$api_home_page,null,$display->getHeaders()
);$display->displayResult('Home Page',$client);$useful_links['create_client'] = $client->getLink('create_client');$resp = $client->post(
$useful_links['create_client'],$data,$display->getHeaders()
);
REST is easy, right? (Nope)● Should every resource have a custom media
type?● How should Hypermedia be represented in
JSON (Collection+JSON, HAL, Siren, etc)?● Link header exclusively or links as part of the
body?● To embedded sub resources?● PATCH/PUT or POST? (X-HTTP-Method-
Override)● Where to put the version number?
REST is easy, right? (Nope)● Include the full resource response when
creating or use a 204?● How do you avoid one PATCH stomping
another?○ ETags and Preconditions
○ "If-None-Match: W/\"9f55f4d0f19b152a6e7c6ddeb4107e486fd7727c\""
○ "If-Modified-Since: Wed, 15 Feb 2012 12:53:52 -0800"
● How do you make hypermedia useful to the client and end user?
● Forms?
YOU NEED TESTS!Functional tests are critical● Ensures your changes haven't broken
anything old or new● Speeds up prototyping
Tests are NOT a substitute for your eyeballs
The FutureReliable platformsConsistent functionalityKnown, shared resources
Notes:http://bestoked.blogspot.com/2012/02/restful-resources-required-reading.htmlhttp://wiki.foxycart.com/v/0.0.0/hypermedia_api