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

Miele per le nostre API

E22edc988280c3b6ff183318bc1590c2?s=47 weLaika
September 26, 2014

Miele per le nostre API

Talk per il Ruby Day 2014

E22edc988280c3b6ff183318bc1590c2?s=128

weLaika

September 26, 2014
Tweet

Transcript

  1. miele per le nostre api best practices in API design

    1 ruby day 2014
  2. who we are a short presentation 2 Fabrizio monti Rails

    developer @delphaber MATTEO PIOTTO Android developer @C1P8 federico parodi iOS developer @jimmy2k
  3. we work with our agencies Turin Milan 3

  4. Why we care? 4

  5. APIs are everywhere It’s funny because in Italian API means

    bees! 5
  6. 6 USE BEST PRACTICES TO AVOID TRAGEDY®

  7. 7 we’ll talk about a lot of useful tips Rest

    JSON Date/time relationship UUID pagination AUTH errors versioning rate limits
  8. 8 we won’t talk about RAILS-api gem It’s just for

    API-only applications, and we need more But we’ll use its component ActiveModelSerializer
  9. 9 clone our code! https://github.com/welaika/rubyday-2014 it’s an api to get

    ruby day 2014 schedule of talks http://api.rubyday-2014.welaika.com
  10. REST 10

  11. REpresentational state transfer 11 every request contains all state information

    the server needs to respond rest
  12. 12 A RESTful, resource-oriented service exposes a URI for every

    piece of data the client might want to operate on rest: nouns http://api.rubyday-2014.welaika.com/v1/talks/:id/ comments
  13. 13 Create => POST Read => GET Update => PUT/PATCH

    Delete => DELETE rest: crud verbs
  14. 14 rest: routing sample Rails.application.routes.draw do ! root to: 'home#show'

    get 'ping', to: 'ping#show' ! namespace :api, path: '/', constraints: {format: :json, subdomain: /^api/} do namespace :v1 do resources :speakers, only: [:index, :show] resources :talks, only: [:index, :show] do resources :comments, only: [:index, :create] end resources :comments, only: [:destroy] resources :schedules, only: [:index] end end ! end
  15. 15 rest: response http status codes 200 ok 201 created

    204 no content 304 not modified 400 bad request 401 unauthorized 403 forbidden 404 not found 405 method not allowed 409 conflict 410 gone 415 unsupported media type 418 i’m a teapot 422 Unprocessable entity 429 too many requests 500 internal error
  16. 16 convention gives us speed in planning and development. !

    common in rails ! it works on any platform. rest: why?
  17. JSON 17

  18. javascript object notation 18 the most practical, readable and used

    format for information sharing JSON
  19. there is no standard for its content 19 there are

    different approaches JSON
  20. an attempt to have a standard 20 guidelines and vocabulary

    for relationships and data structures every response must be a document or a collection of documents standard names for common fields mandatory links to schema and self JSON: jsonspec http://jsonspec.org
  21. they are based on the needs of your project 21

    find a balance between too many subresources and a fat single resource with nested objects JSON: relationships
  22. foreign key arrays 22 JSON: relationships { “post”: { “id”:

    1, “title”: “Lorem ipsum dolor sit amet”, “comments”: [1, 2], “_links”: { “user”: “/people/c1p8” } } }
  23. nested foreign key relation 23 JSON: relationships “author_id”: “5d8201b0” “author”:

    { “id”: “5d8201b0” } “author”: { “id”: “5d8201b0”, “name”: “Mario Rossi”, “email”: “mario.rossi@welaika.com” }
  24. compound documents (side loading) 24 JSON: relationships { “meta”: {

    “primaryCollection”: “comments” }, “comments”: […], “authors”: […] }
  25. nested documents 25 JSON: relationships { “id”: 24343, “name”: “weLaika”,

    “cover": { "cover_id": "10151957472986851", "offset_x": 0, "offset_y": 45, "source": “https://example.net/123.jpg" }, “lat”: 45.092406, “lon”: 7.664938, “created_at”: “2014-09-26” }
  26. 26 JSON: our apis response { "speakers": [ { "id":

    "79f80f48-4421-46b9-842b-4c21ca55b704", "timestamps": { "created_at": "2014-09-25T15:23:58.578Z", "updated_at": "2014-09-25T15:23:58.578Z" }, "first_name": "Fabrizio", "last_name": "Monti", "twitter_username": "delphaber", "talk_ids": [ "19cfa128-50a1-449b-81b7-e29e12412faa" ] }, […] ] } HTTP/1.1 200 OK Server: Cowboy Connection: close Date: Thu, 25 Sep 2014 17:46:47 GMT Status: 200 OK Content-Type: application/json; charset=utf-8 Vary: Accept-Encoding Etag: "a39088c567ffeda98b34000ae21007cb" Cache-Control: max-age=0, private, must- revalidate curl -i -H "Accept: application/json" http://api.rubyday-2014.welaika.com/v1/speakers
  27. 27 JSON: our apis response class SpeakerSerializer < ApplicationSerializer attributes

    :first_name, :last_name, :twitter_username ! has_many :talks, embed: :ids end app/serializers/speaker_serializer.rb
  28. 28 JSON: our apis response { "talks": [ { "id":

    "5d7fc945-f319-48b1-a738-bae526e24c9e", "timestamps": { "created_at": "2014-09-25T15:23:58.665Z", "updated_at": "2014-09-25T15:23:58.665Z" }, "title": "Service Oriented Architecture for robust and scalable systems", "description": "Software Architecture is hard. And when your business grows…”, "language": "en", "speaker_ids": [ "6d96629c-b2aa-4cc1-b019-f0281710cb44" ], "schedule_id": "e967bc31-020f-429d-8330-9aec3a1b4a45", "classroom_id": "14c41717-a178-4b2f-aa63-404557820efe" }, […] ] } HTTP/1.1 200 OK Server: Cowboy Connection: close Date: Thu, 25 Sep 2014 19:05:28 GMT Status: 200 OK Content-Type: application/json; charset=utf-8 Vary: Accept-Encoding Etag: "448a532c795d09f3d8442c72f234ed6b" Cache-Control: max-age=0, private, must- revalidate curl -i -H "Accept: application/json" http://api.rubyday-2014.welaika.com/v1/talks
  29. 29 JSON: our apis response class TalkSerializer < ApplicationSerializer attributes

    :title, :description, :language ! has_many :speakers, embed: :ids has_one :schedule, embed: :ids has_one :classroom, embed: :ids ! end app/serializers/talk_serializer.rb
  30. 30 JSON: our apis response { "schedules": [ { "id":

    "53acb2bf-75d0-4afd-a7b9-6d0e6bad8048", "timestamps": { "created_at": "2014-09-25T15:23:58.863Z", "updated_at": "2014-09-25T15:23:58.863Z" }, "start_at": "2014-09-26T08:30:00.000Z", "finish_at": "2014-09-26T09:29:59.000Z", "note": null, "talk_id": "ce0238e9-5f98-491b-9212-e40d0916b068", "classroom": { "id": "834be8de-6ff6-4fa7-89fc-f8a6ebf3c7b5", "created_at": "2014-09-25T15:23:58.724Z", "updated_at": "2014-09-25T15:23:58.724Z", "name": "Second" } }, [...] ] } HTTP/1.1 200 OK Server: Cowboy Connection: close Date: Thu, 25 Sep 2014 19:05:28 GMT Status: 200 OK Content-Type: application/json; charset=utf-8 Vary: Accept-Encoding Etag: "448a532c795d09f3d8442c72f234ed6b" Cache-Control: max-age=0, private, must- revalidate curl -i -H "Accept: application/json" http://api.rubyday-2014.welaika.com/v1/schedules? classroom=834be8de-6ff6-4fa7-89fc-f8a6ebf3c7b5
  31. 31 JSON: our apis response class ScheduleSerializer < ApplicationSerializer attributes

    :start_at, :finish_at, :note ! has_one :talk, embed: :ids has_one :classroom ! end app/serializers/schedule_serializer.rb
  32. dates 32

  33. 33 date representation one of the most difficult task in

    IT 26/09/2014 09/26/14 2014年9⽉月26⽇日 12h 24h
  34. 34 date representation one of the most difficult task in

    IT
  35. 35 date representation one of the most difficult task in

    IT iso 8601 & UTC TIME 2014-09-26T10:00:00Z 2014-09-26 2014-W39 2014-W39-4 2014-268
  36. IDs 36

  37. 37 IDS don’t screw’em up prefer UUIDs over sequential IDS

    UUIDs are available as type in PostgreSQL
  38. 38 IDS don’t screw’em up enable_extension "uuid-ossp" ! create_table "classrooms",

    id: :uuid do |t| t.datetime "created_at" t.datetime "updated_at" t.string "name", null: false end
  39. pagination 39

  40. 40 pagination get parameters http headers two ways to do

    it
  41. 41 pagination: get parameters curl -i -H "Accept: application/json" http://

    api.rubyday-2014.welaika.com/v1/talks/19cfa128-50a1/comments?page=2 { "comments": [ { "id": "980c16b7-0772-4deb-b62d-ab09acb6c360", "timestamps": { "created_at": "2014-09-25T15:23:58.925Z", "updated_at": "2014-09-25T15:23:58.925Z" }, "content": "0 bottles of beer on the wall." }, […] ], "meta": { "pagination": { "current_page": 2, "next_page": 3, "prev_page": 1, "total_pages": 4, "total_count": 100 } } } HTTP/1.1 200 OK Server: Cowboy Connection: close Date: Thu, 25 Sep 2014 17:59:51 GMT Status: 200 OK Content-Type: application/json; charset=utf-8 Vary: Accept-Encoding Etag: "28a90d7d8855ad9a02800860a34f22c3" Cache-Control: max-age=0, private, must- revalidate
  42. 42 pagination: get parameters module API module V1 class CommentsController

    < VersionController ... def index @comments = @talk.comments.chronologically.page(params[:page]) respond_with @comments, status: 200, serializer: PaginationSerializer end ... end end end app/controllers/api/v1/comments_controller.rb
  43. 43 pagination: get parameters class PaginationSerializer < ActiveModel::ArraySerializer def initialize(object,

    options={}) meta_key = options[:meta_key] || :meta options[meta_key] ||= {} options[meta_key][:pagination] = { current_page: object.current_page, next_page: object.next_page, prev_page: object.prev_page, total_pages: object.total_pages, total_count: object.total_count } super(object, options) end end app/serializers/pagination_serializer.rb
  44. Auth 44

  45. 45 AUTH APIS are stateless, you need a way to

    authenticate yourself each time
  46. 46 AUTH: basic AUTH super easy, username and password are

    encoded in base64 and included in url super dangerous outside https
  47. 47 AUTH: access token class APIController < ActionController::Base protect_from_forgery with:

    :null_session ! protected ! def authenticate! authenticate_token || render_unauthorized end ! def authenticate_token authenticate_with_http_token do |token, options| token == (ENV['RUBYDAY_AUTH_TOKEN'] || "quarantadue") end end ! def render_unauthorized render json: {error: 'Bad credentials'}, status: 401 end ! end
  48. 48 AUTH: access token curl -i -X POST -d '{"comment":

    {"content": "prova"}}' -H "Content-Type: application/ json" -H "Accept: application/json" -H "Authorization: Token token=sbagliato" http:// api.rubyday-2014.welaika.com/v1/talks/19cfa128-50a1-/comments HTTP/1.1 401 Unauthorized Server: Cowboy Connection: close Date: Thu, 25 Sep 2014 18:27:17 GMT Status: 401 Unauthorized Content-Type: application/json; charset=utf-8 Vary: Accept-Encoding Cache-Control: no-cache Via: 1.1 vegur ! {"error":"Bad credentials"}
  49. 49 AUTH: oauth different versions available more difficult to implement

    more secure
  50. errors 50

  51. 51 ERRORS http statuses human friendly error in response body

    for post and put errors, add a sub-error with a code and description
  52. 52 ERRORS curl -i -X POST -d '{"comment": {"content": ""}}'

    -H "Content-Type: application/json" - H "Accept: application/json" -H "Authorization: Token token=rubyday" http:// api.rubyday-2014.welaika.com/v1/talks/19cfa128-50a1/comments HTTP/1.1 422 Unprocessable Entity Server: Cowboy Connection: close Date: Thu, 25 Sep 2014 18:29:39 GMT Status: 422 Unprocessable Entity Content-Type: application/json; charset=utf-8 Vary: Accept-Encoding Cache-Control: no-cache ! {"error":"Validation error","messages":{"content":["can't be blank"]}}
  53. versioning 53

  54. “there is nothing wrong with change, if it is in

    the right direction”. 54 your apis will change there’s no standard way to communicate version change versioning (CHURCHILL)
  55. 55 major version in url minor version in custom http

    header deprecations to offer backward compatibility versioning: stripe’s way
  56. 56 versioning: our APIS Rails.application.routes.draw do ! root to: 'home#show'

    get 'ping', to: 'ping#show' ! namespace :api, path: '/', constraints: {format: :json, subdomain: /^api/} do namespace :v1 do resources :speakers, only: [:index, :show] resources :talks, only: [:index, :show] do resources :comments, only: [:index, :create] end # no need to be nested in :talks resources :comments, only: [:destroy] resources :schedules, only: [:index] end end ! end app/ #$$ assets #$$ controllers % #$$ api % % &$$ v1 % % #$$ comments_controller.rb % % #$$ schedules_controller.rb % % #$$ speakers_controller.rb % % #$$ talks_controller.rb % % &$$ version_controller.rb % #$$ api_controller.rb % #$$ application_controller.rb
  57. 57 versioning: our APIS module API module V1 class CommentsController

    < VersionController [...] end end end app/controllers/api/v1/comments_controller.rb
  58. rate limit 58

  59. 429 too many requests 59 you have to limit requests

    of expensive resources use http headers to communicate status of limits rate limit
  60. 60 rate limit $ curl -i https://api.github.com/users/octocat/orgs ! HTTP/1.1 200

    OK Server: nginx Date: Fri, 12 Oct 2012 23:33:14 GMT Content-Type: application/json; charset=utf-8 Connection: keep-alive Status: 200 OK ETag: "a00049ba79152d03380c34652f2cb612" X-GitHub-Media-Type: github.v3 X-RateLimit-Limit: 5000 X-RateLimit-Remaining: 4987 X-RateLimit-Reset: 1350085394 Content-Length: 5 Cache-Control: max-age=0, private, must-revalidate X-Content-Type-Options: nosniff
  61. compression 61

  62. 62 compression it’s about time and money, and time is

    money just use gzip it saves a lot of resources, about 1/5 of uncompressed response size not just for apis
  63. 63 compression it’s about time and money, and time is

    money config.middleware.use Rack::Deflater in rails, add in config/application.rb:
  64. 64 compression it’s about time and money, and time is

    money GZipped: 2014-09-25T19:08:06.597948+00:00 heroku[router]: at=info method=GET path="/v1/talks" host=api.rubyday-2014.welaika.com request_id=4a968470-7443-4400-ba56-0a8a933a6921 fwd="93.54.94.77" dyno=web.1 connect=0ms service=37ms status=200 bytes=4706 ! Uncompressed: 2014-09-25T19:08:30.898472+00:00 heroku[router]: at=info method=GET path="/v1/talks" host=api.rubyday-2014.welaika.com request_id=d31d9b95-29bf-4a86-b72b-555f3f9bd856 fwd="93.54.94.77" dyno=web.1 connect=1ms service=37ms status=200 bytes=10295
  65. 65 thank you http://dev.welaika.com http://cayenne.it Question time! https://github.com/welaika/rubyday-2014

  66. 66 get the cheat sheet or you won’t remember anything

    http://l.welaika.com/api_cheatsheet
  67. 67 credits Icons made by Freepik from www.flaticon.com is licensed

    under CC BY 3.0 Bibliography: “How to Design a REST API and Why You Should” (http://goo.gl/Ms85sU) “When crafting your API strategy, put design first” (http://goo.gl/N1OnPT) “Best Practices for Designing a Pragmatic RESTful API” (http://goo.gl/1yTjC) “HTTP API Design Guide” (http://goo.gl/slrurt) Code School (http://codeschool.com) Faces made with