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

Miele per le nostre API

weLaika
September 26, 2014

Miele per le nostre API

Talk per il Ruby Day 2014

weLaika

September 26, 2014
Tweet

More Decks by weLaika

Other Decks in Programming

Transcript

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

    developer @delphaber MATTEO PIOTTO Android developer @C1P8 federico parodi iOS developer @jimmy2k
  2. 7 we’ll talk about a lot of useful tips Rest

    JSON Date/time relationship UUID pagination AUTH errors versioning rate limits
  3. 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
  4. 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
  5. 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
  6. 13 Create => POST Read => GET Update => PUT/PATCH

    Delete => DELETE rest: crud verbs
  7. 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
  8. 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
  9. 16 convention gives us speed in planning and development. !

    common in rails ! it works on any platform. rest: why?
  10. 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
  11. 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
  12. foreign key arrays 22 JSON: relationships { “post”: { “id”:

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

    { “id”: “5d8201b0” } “author”: { “id”: “5d8201b0”, “name”: “Mario Rossi”, “email”: “[email protected]” }
  14. compound documents (side loading) 24 JSON: relationships { “meta”: {

    “primaryCollection”: “comments” }, “comments”: […], “authors”: […] }
  15. 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” }
  16. 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
  17. 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
  18. 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
  19. 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
  20. 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
  21. 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
  22. 33 date representation one of the most difficult task in

    IT 26/09/2014 09/26/14 2014年9⽉月26⽇日 12h 24h
  23. 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
  24. 37 IDS don’t screw’em up prefer UUIDs over sequential IDS

    UUIDs are available as type in PostgreSQL
  25. 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
  26. 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
  27. 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
  28. 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
  29. 45 AUTH APIS are stateless, you need a way to

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

    encoded in base64 and included in url super dangerous outside https
  31. 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
  32. 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"}
  33. 51 ERRORS http statuses human friendly error in response body

    for post and put errors, add a sub-error with a code and description
  34. 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"]}}
  35. “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)
  36. 55 major version in url minor version in custom http

    header deprecations to offer backward compatibility versioning: stripe’s way
  37. 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
  38. 57 versioning: our APIS module API module V1 class CommentsController

    < VersionController [...] end end end app/controllers/api/v1/comments_controller.rb
  39. 429 too many requests 59 you have to limit requests

    of expensive resources use http headers to communicate status of limits rate limit
  40. 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
  41. 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
  42. 63 compression it’s about time and money, and time is

    money config.middleware.use Rack::Deflater in rails, add in config/application.rb:
  43. 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
  44. 66 get the cheat sheet or you won’t remember anything

    http://l.welaika.com/api_cheatsheet
  45. 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