$30 off During Our Annual Pro Sale. View Details »

Designing Hypermedia APIs

Designing Hypermedia APIs

I gave this talk at TwilioCon 2012.

Steve Klabnik

October 17, 2012
Tweet

More Decks by Steve Klabnik

Other Decks in Programming

Transcript

  1. Designing
    Hypermedia
    APIs
    @steveklabnik
    Wednesday, October 17, 12

    View Slide

  2. Wednesday, October 17, 12

    View Slide

  3. “People are fairly good at short-term
    design, and usually awful at long-term
    design. Most don’t think they need to
    design past the current release.”
    Problem
    Wednesday, October 17, 12

    View Slide

  4. Solution?
    Wednesday, October 17, 12

    View Slide

  5. Solution
    Flexibility
    Stability
    Wednesday, October 17, 12

    View Slide

  6. “But what distinguishes the worst
    architect from the best of bees is this, that
    the architect raises his structure in
    imagination before he erects it in reality.”
    Karl Marx
    Wednesday, October 17, 12

    View Slide

  7. GET / HTTP/1.1
    Host: www.example.com
    HTTP/1.1 200 OK
    Content-Length: 438
    Content-Type: application/json
    Link: ; rel="profile"
    {
    "statuses":[
    {
    "body":"neato",
    "username":"steveklabnik"
    },
    {
    "body":"testing testing",
    "username":"steveklabnik"
    }
    ],
    "template":{
    "body":"",
    "username":""
    },
    "links":[
    {
    "rel":"collection",
    "href":"/"
    }
    ]
    }
    Wednesday, October 17, 12

    View Slide

  8. GET / HTTP/1.1
    Host: www.example.com
    HTTP/1.1 200 OK
    Content-Length: 438
    Content-Type: application/json
    Link: ; rel="profile"
    {
    "statuses":[
    {
    "body":"neato",
    "username":"steveklabnik"
    },
    {
    "body":"testing testing",
    "username":"steveklabnik"
    }
    ],
    "template":{
    "body":"",
    "username":""
    },
    "links":[
    {
    "rel":"collection",
    "href":"/"
    }
    ]
    }
    Wednesday, October 17, 12

    View Slide

  9. GET / HTTP/1.1
    Host: www.example.com
    HTTP/1.1 200 OK
    Content-Length: 438
    Content-Type: application/json
    Link: ; rel="profile"
    {
    "statuses":[
    {
    "body":"neato",
    "username":"steveklabnik"
    },
    {
    "body":"testing testing",
    "username":"steveklabnik"
    }
    ],
    "template":{
    "body":"",
    "username":""
    },
    "links":[
    {
    "rel":"collection",
    "href":"/statuses"
    }
    ]
    }
    Wednesday, October 17, 12

    View Slide

  10. POST / HTTP/1.1
    Host: www.example.com
    {
    "body":"hello, hypermedia",
    "username":"steveklabnik"
    }
    HTTP/1.1 201 Created
    Content-Length: 438
    Content-Type: application/json
    Link: ; rel="profile"
    Location: "/"
    {"message":"you are being redirected"}
    Wednesday, October 17, 12

    View Slide

  11. GET / HTTP/1.1
    Host: www.example.com
    HTTP/1.1 200 OK
    Content-Length: 438
    Content-Type: application/json
    Link: ; rel="profile"
    {
    "statuses":[
    {
    "body":"neato",
    "username":"steveklabnik"
    },
    {
    "body":"testing testing",
    "username":"steveklabnik"
    },
    {
    "body":"hello, hypermedia",
    "username":"steveklabnik"
    },
    ],
    "template":{
    "body":"",
    "username":""
    },
    "links":[
    {
    "rel":"collection",
    "href":"/"
    }
    ]}
    Wednesday, October 17, 12

    View Slide

  12. Adapters
    Wednesday, October 17, 12

    View Slide

  13. Adapters
    Wednesday, October 17, 12

    View Slide

  14. Coupling - Demeter
    class Foo
    def initialize(bar)
    @bar = bar
    end
    def process
    @bar.qux.fetch_data
    end
    end
    Wednesday, October 17, 12

    View Slide

  15. Coupling - Demeter
    class Foo
    def initialize(bar)
    @bar = bar
    end
    def process
    @bar.qux.fetch_data
    end
    end
    Wednesday, October 17, 12

    View Slide

  16. Coupling - Demeter
    describe Foo do
    it “fetches data” do
    bar = Bar.new
    bar.stub(:qux =>
    stub(:fetch_data => “data”)
    )
    expect(Foo.new(bar).process).to eq(“data”)
    end
    end
    Wednesday, October 17, 12

    View Slide

  17. Coupling - Demeter
    describe Foo do
    it “fetches data” do
    bar = Bar.new
    bar.stub(:qux =>
    stub(:fetch_data => “data”)
    )
    expect(Foo.new(bar).process).to eq(“data”)
    end
    end
    Wednesday, October 17, 12

    View Slide

  18. Coupling - Demeter
    Demeter is actually
    about types.
    @bar.qux.fetch_data
    Wednesday, October 17, 12

    View Slide

  19. Coupling - Demeter
    You can tell something is
    coupled because it breaks
    when you change it.
    Wednesday, October 17, 12

    View Slide

  20. Coupling - Demeter
    Stability: doesn’t break over time
    Flexibility: ability to change
    Wednesday, October 17, 12

    View Slide

  21. Solution
    Radical
    decoupling
    Wednesday, October 17, 12

    View Slide

  22. Decoupling
    Wednesday, October 17, 12

    View Slide

  23. Decoupling
    Wednesday, October 17, 12

    View Slide

  24. Decoupling
    GET / HTTP/1.1
    Host: www.example.com
    Accept: application/json
    HTTP/1.1 200 OK
    Content-Length: 438
    Content-Type: application/json
    Wednesday, October 17, 12

    View Slide

  25. Decoupling
    GET / HTTP/1.1
    Host: www.example.com
    Accept: application/json
    HTTP/1.1 200 OK
    Content-Length: 438
    Content-Type: application/json
    Wednesday, October 17, 12

    View Slide

  26. Media Types
    Media type definition contains all
    information needed to implement a
    client and server for a given type.
    Wednesday, October 17, 12

    View Slide

  27. GET / HTTP/1.1
    Host: www.example.com
    HTTP/1.1 200 OK
    Content-Length: 438
    Content-Type: application/json
    Link: ; rel="profile"
    {
    "statuses":[
    {
    "body":"neato",
    "username":"steveklabnik"
    },
    {
    "body":"testing testing",
    "username":"steveklabnik"
    },
    {
    "body":"hello, hypermedia",
    "username":"steveklabnik"
    },
    ],
    "template":{
    "body":"",
    "username":""
    },
    "links":[
    {
    "rel":"collection",
    "href":"/"
    }
    ]}
    Wednesday, October 17, 12

    View Slide

  28. GET /profile HTTP/1.1
    Host: www.example.com
    HTTP/1.1 200 OK
    Content-Length: 438
    Content-Type: text/plain
    This server emits "microblogging JSON."
    ## Keys
    You can expect the following keys: statuses,
    template, links
    ## Link Relations
    You can expect a "collection" relation.
    GETing the URI will fetch the list of all
    statuses. POSTing a template to this URI will
    create a new status.
    Wednesday, October 17, 12

    View Slide

  29. Media Types
    Wednesday, October 17, 12

    View Slide

  30. Media Types
    Clients
    Servers
    Media Type “microblog JSON”
    Wednesday, October 17, 12

    View Slide

  31. “independent
    evolution”
    Media Types
    Wednesday, October 17, 12

    View Slide

  32. Media Types
    https://dev.twitter.com/docs
    Wednesday, October 17, 12

    View Slide

  33. Media Types
    RPC
    Wednesday, October 17, 12

    View Slide

  34. Media Types
    def reply_to_tweet
    tweets = get_list_of_tweets
    reply_to = display_to_user(tweets)
    reply_data = display_require_form
    send_reply(reply_to, reply_data)
    end
    Wednesday, October 17, 12

    View Slide

  35. Media Types
    def reply_to_tweet
    tweets = get_list_of_tweets
    reply_to = display_to_user(tweets)
    reply_data = display_require_form
    send_reply(reply_to, reply_data)
    end
    Wednesday, October 17, 12

    View Slide

  36. Media Types
    Wednesday, October 17, 12

    View Slide

  37. Media Types
    Wednesday, October 17, 12

    View Slide

  38. Media Types




    Wednesday, October 17, 12

    View Slide

  39. Media Types
    Wednesday, October 17, 12

    View Slide

  40. Media Types
    Hypermedia Affordance
    foo




    Wednesday, October 17, 12

    View Slide

  41. Media Types
    Wednesday, October 17, 12

    View Slide

  42. Media Types
    Collection
    +JSON,
    HAL, XHTML
    Wednesday, October 17, 12

    View Slide

  43. Pagination
    The pagination info is included in the Link header. It is important to
    follow these Link header values instead of constructing your own
    URLs. In some instances, such as in the Commits API, pagination is
    based on SHA1 and not on page number.
    Link: page=3&per_page=100>; rel="next",
    page=50&per_page=100>; rel="last"
    Wednesday, October 17, 12

    View Slide

  44. ID -> URI
    "id": 2
    vs.
    {"rel": "self",
    "href": "http://localhost:9292/status/2"}
    Wednesday, October 17, 12

    View Slide

  45. Hypermedia Proxy Pattern
    {"links": [
    {"rel": "self",
    "href": "http://localhost:9292/status/
    2"}],
    "body":"hello, world",
    "location":"Los Angeles"
    }
    Wednesday, October 17, 12

    View Slide

  46. Hypermedia Proxy Pattern
    {"links": [
    {"rel": "self",
    "href": "http://localhost:9292/status/
    2"}],
    "body":"hello, world"
    }
    Wednesday, October 17, 12

    View Slide

  47. Hypermedia Proxy Pattern
    class Status
    attr_accessor :self_rel, :body
    def initialize(opts={})
    @body = opts['body']
    @location = opts['location']
    @self_rel = opts['links'].find{|link| link['rel'] == "self"}['href']
    end
    def location
    @location ||= begin
    fetch_data(self_rel)['location']
    end
    end
    end
    Wednesday, October 17, 12

    View Slide

  48. Thanks!
    @steveklabnik
    [email protected]
    http://designinghypermediaapis.com
    http://words.steveklabnik.com
    http://tutorials.jumpstartlab.com
    Wednesday, October 17, 12

    View Slide