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

Implementing a RESTful API with Ruby

Implementing a RESTful API with Ruby

APIs are often one the most powerful vectors for driving product adoption, but are often times bolted onto a SaaS offering as an after-thought. There are many different interpretations (some much looser than others) of what makes an API “RESTful”. At Librato we practice “API-first” development where our API’s are specified, tested, implemented, and finally used to add new features to our own products prior to providing them to our users.

Joseph Ruscio

September 21, 2011
Tweet

More Decks by Joseph Ruscio

Other Decks in Programming

Transcript

  1. Implementing a
    RESTful API w/Ruby
    “The Right Way”
    Wednesday, September 21, 2011

    View Slide

  2. metrics.librato.com
    Increase service flexibility/utility
    Free users’ data
    Build business value
    Why APIs?
    Wednesday, September 21, 2011

    View Slide

  3. metrics.librato.com
    Consumes arbitrary time series data
    Store it, visualize it, analyze it
    Query it back out from API
    Rich client interface
    Librato Metrics
    Wednesday, September 21, 2011

    View Slide

  4. metrics.librato.com
    Wednesday, September 21, 2011

    View Slide

  5. metrics.librato.com
    Requirements
    Easy to implement and maintain
    Performant and scalable
    Easy to understand and use
    Wednesday, September 21, 2011

    View Slide

  6. metrics.librato.com
    What is REST?
    Wednesday, September 21, 2011

    View Slide

  7. metrics.librato.com
    Wednesday, September 21, 2011

    View Slide

  8. metrics.librato.com
    L2 - CRUD
    URIs
    HTTP Methods
    Wednesday, September 21, 2011

    View Slide

  9. metrics.librato.com
    L3 - REST
    REpresentational
    State Transfer
    HATEOAS
    Hypermedia
    Constraint
    Wednesday, September 21, 2011

    View Slide

  10. metrics.librato.com
    “[hypertext constraint is] most often
    violated within so-called REST APIs ... adhere
    to them or choose some other buzzword
    for your API.”
    - Roy Fielding
    Wednesday, September 21, 2011

    View Slide

  11. metrics.librato.com
    CRUD API
    Resources identified/addressed by URI
    URI Templates
    HTTP-based interactions
    Wednesday, September 21, 2011

    View Slide

  12. metrics.librato.com
    Resources
    Anything we expose to the Web
    Resources have representations
    JSON,XML,PNG
    Services exchange representations
    Wednesday, September 21, 2011

    View Slide

  13. metrics.librato.com
    URIs
    Uniform Resource Identifier
    Uniquely identifies a web resource
    Makes it addressable
    http://example.org/resource/123
    Wednesday, September 21, 2011

    View Slide

  14. metrics.librato.com
    API Design Constraints
    JSON output
    JSON and x-form-urlencoded input
    Basic HTTP Authentication over SSL
    Wednesday, September 21, 2011

    View Slide

  15. metrics.librato.com
    Doc. Driven Design
    URI template
    Markdown file per-resource
    URI, parameters, examples
    Production ready before a single LOC!
    Wednesday, September 21, 2011

    View Slide

  16. metrics.librato.com
    Wednesday, September 21, 2011

    View Slide

  17. metrics.librato.com
    Test Driven Dev.
    Simulate requests against the API
    Assert expectations about the response
    test-unit + rack-test
    Wednesday, September 21, 2011

    View Slide

  18. metrics.librato.com
    Test Driven Dev.
    def test_create_gauge
    params = {}
    params[:name] = "my_gauge"
    post "/gauges", params
    assert_equal 201, last_response.status
    assert last_response.original_headers["Location"] =~
    /^\/v1\/gauges\/#{params[:name]}$/
    end
    Wednesday, September 21, 2011

    View Slide

  19. metrics.librato.com
    Wednesday, September 21, 2011

    View Slide

  20. metrics.librato.com
    Create Resource
    POST /resource
    Location:
    201 - Created
    def test_create
    create_resource(
    :name => "some_gauge",
    :period => 60
    )
    create_resource(
    :code => 400,
    :name => "a" * 256
    )
    end
    Wednesday, September 21, 2011

    View Slide

  21. metrics.librato.com
    Read Resource
    GET /resource/:id
    200 OK
    def test_read
    name = "my_gauge"
    gauge =
    read_resource(:id => name)
    assert_equal(name,
    gauge['name'])
    end
    Wednesday, September 21, 2011

    View Slide

  22. metrics.librato.com
    Update Resource
    PUT /resource/:id
    204 - No Content
    def test_update
    name = "my_gauge"
    update_resource(:name => name,
    :period => 300)
    end
    Wednesday, September 21, 2011

    View Slide

  23. metrics.librato.com
    Delete Resource
    DELETE /resource/:id
    204 No Content
    def test_delete
    name = "my_gauge"
    delete_resource(:id => name)
    read_resource(:code => 404,
    :id => name)
    end
    Wednesday, September 21, 2011

    View Slide

  24. metrics.librato.com
    Implementation
    Wednesday, September 21, 2011

    View Slide

  25. metrics.librato.com
    Implementation
    CRUD API is a thin layer over your models
    Sinatra + Sequel
    Resource per file
    to_hash method per-model
    Wednesday, September 21, 2011

    View Slide

  26. metrics.librato.com
    Implementation
    # CREATE
    post "/gauges" do
    unless params[:name]
    abort_params({:name => ["Must specify gauge name"]})
    end
    if find_gauge(params[:name])
    abort_unique({:name => ["Gauge name must be unique"]})
    end
    gauge = create_gauge()
    halt_created("/gauges/#{gauge.name}")
    end
    Wednesday, September 21, 2011

    View Slide

  27. metrics.librato.com
    Wednesday, September 21, 2011

    View Slide

  28. metrics.librato.com
    Pagination Request
    offset - starting index into found
    length - # of resources requested
    orderby - attribute to sort on
    sort - ASC/DESC
    Wednesday, September 21, 2011

    View Slide

  29. metrics.librato.com
    Pagination Response
    total - # of resources
    found - # of resources that match the query
    offset - starting index into found
    length - # of resources in response
    Wednesday, September 21, 2011

    View Slide

  30. metrics.librato.com
    Authentication
    Secure access through SSL/TLS
    Block port 80 if possible
    403 all HTTP requests otherwise
    Basic Authentication
    Wednesday, September 21, 2011

    View Slide

  31. metrics.librato.com
    API Versioning
    URI templates are static
    Prefer to maintain compatibility
    Failing that prefer to version your entire API
    URI? Media Type? Custom Header?
    Wednesday, September 21, 2011

    View Slide

  32. metrics.librato.com
    URI
    http://v1.example.org/resource/123
    http://example.org/v1/resource/123
    http://example.org/resource/123 ?v=1
    Wednesday, September 21, 2011

    View Slide

  33. metrics.librato.com
    Media Type
    Negotiate through Accept header
    application/json;version=1
    Wednesday, September 21, 2011

    View Slide

  34. metrics.librato.com
    Custom HTTP Header
    X-Api-Version: 1
    May not work with legacy clients
    Don’t do this!
    Wednesday, September 21, 2011

    View Slide

  35. metrics.librato.com
    Partial Updates
    PATCH - RFC5789
    PATCH /resource/:id
    Wednesday, September 21, 2011

    View Slide

  36. metrics.librato.com
    HATEOAS
    CRUD is great except when it isn’t
    Hypermedia models state transitions
    Inclusion of links in representation
    Wednesday, September 21, 2011

    View Slide

  37. metrics.librato.com
    Hypermedia Formats
    XHTML, ATOM, RSS
    Domain-specific formats!
    Add link elements to CRUD format
    Wednesday, September 21, 2011

    View Slide

  38. metrics.librato.com
    CRUD Pagination
    {
    "query":{
    "total":128,
    "found":128,
    "offset":0,
    "length":100
    },
    "gauges":[
    { "name":"gauge1" },
    { "name":"gauge2" },
    ...
    ]
    }
    Wednesday, September 21, 2011

    View Slide

  39. metrics.librato.com
    REST Pagination
    {
    "next":{
    "uri":"http://api.librato.com/gauges?offset=100”,
    "rel":"http://relations.librato.com/paginate",
    "media_type":"application/vnd.librato+json"
    },
    "gauges":[
    { "name":"gauge1" },
    { "name":"gauge2" },
    ...
    ]
    }
    Wednesday, September 21, 2011

    View Slide

  40. metrics.librato.com
    Link Elements
    uri attribute describes resource
    rel attribute contains semantic markup
    link elements can be added/removed from
    representations
    Wednesday, September 21, 2011

    View Slide

  41. metrics.librato.com
    GLory of REST
    Single bookmarked URI
    Next states are discovered inline
    Series of representation exchanges transforms
    application state
    Wednesday, September 21, 2011

    View Slide

  42. metrics.librato.com
    Have an API and build it first
    At least use CRUD
    Docs -> Tests -> Code
    Make it DRY
    Dogfood It
    Learn more about REST
    Wednesday, September 21, 2011

    View Slide

  43. metrics.librato.com
    Wednesday, September 21, 2011

    View Slide

  44. metrics.librato.com
    Wednesday, September 21, 2011

    View Slide

  45. metrics.librato.com
    Wednesday, September 21, 2011

    View Slide

  46. metrics.librato.com
    “be conservative in what you do, be liberal
    in what you accept from others”
    - Postel’s Law
    Wednesday, September 21, 2011

    View Slide

  47. metrics.librato.com
    Q&A
    Wednesday, September 21, 2011

    View Slide