10 Things to Make API Users Like You

Click here to load reader

download 10 Things to Make API Users Like You

of 40

  • date post

    27-Aug-2014
  • Category

    Software

  • view

    339
  • download

    3

Embed Size (px)

description

Having experiences of developing iCook API, I'd like to share some gems, skills and tips to build an efficient, consistent and well-documented API with Ruby on Rails which makes API users like you more.

Transcript of 10 Things to Make API Users Like You

  • 10 Things to Make API Users Like You @abookyun - David API 10
  • iCook The largest recipes sharing website in Taiwan. - 2012 Polydice Backend Engineer - iCook Polydice - iCook Ruby on Rails -
  • iCook Were ready for! iOS, Android and Windows - iCook app Windows 8 app - app server API API
  • 40,000 recipes 600,000 members 1,000,000 downloads 1,600,000 API calls per day - API calls 160
  • Principles
  • Principles Documentation Consistent Efcient From routes, request & response data, template Automatic generated, updated, understandable Fast, as small as we requested - API CDE - routes request & response - API Users - API response time requests -
  • Diary - Diary - API = dish = comment = user
  • RESTful and Reasonable routes - RESTful - Rails Framework RESTful - routes RESTful Reasonable
  • RESTful and Reasonable routes # app/models/comment.rb class Comment < ActiveRecord::Base belongs_to :commentable, polymorphic: true end ! # routes.rb resources :dishes do resources :comments, to: "dishes/comments" end ! # GET /dishes/1/comments # POST /dishes/1/comments # DELETE /dishes/1/comments/1 - dishes has_many comments routes - comments polymorphic type DELETE ID
  • RESTful and Reasonable routes # routes.rb resources :dishes do resources :comments, to: dishes/comments" , except: [:destroy] end resources :comments, only: [:destroy] ! # GET /dishes/1/comments # POST /dishes/1/comments # DELETE /comments/1 - routes API Users
  • Implicit in routes - RESTful route resource - API Users API Users API
  • Implicit in routes resources :users do resources :settings, to: "users/settings" end ! # GET /users/username/settings # PUT /users/username/settings ! resources :settings ! # GET /settings # PUT /settings - routes username /
  • Debate on page vs. offset - API Users API - edge case
  • Debate on page vs. offset { page: 2, per_page: 2 }! { offset: 2, limit: 2 } :) :) :( :@ 1 2 3 4 5 -
  • Debate on page vs. offset { page: 2, per_page: 2 } :) :) :( :@ 1 2 3 4 5 { offset:1, limit: 2 } - - local objects offset = 1 - page/per_page - app - page/per_page Kaminari will_paginate gem limit/offset API Users - - Facebook, Twitter
  • Documentation - API Users -
  • - Given: RSpec and TravisCI! - Requirement:! - light-weight! - generate docs based on specs! - generate docs in html format! - We found square/fdoc Documentation - - specs - - API Users - endpoint - square fdoc
  • Documentation # spec/controllers/members require 'fdoc/spec_watcher' ! describe MembersController do include Fdoc::SpecWatcher context '#show', fdoc: 'members/list' do # ... end end ! FDOC_SCAFFOLD=true bundle exec rspec spec - Spec - YAML()
  • Documentation # docs/fdoc/members/list-GET.fdoc description: The list of members. requestParameters: properties: limit: type: integer required: no default: 50 description: Limits the number of results returned. responseParameters: properties: members: type: array items: title: member description: Representation of a member type: object properties: name: description: Member's name type: string required: yes example: Captain Smellypants responseCodes: - status: 200 OK successful: yes description: A list of current members - status: 400 Bad Request successful: no description: Indicates malformed parameters - requestresponseresponseCodes
  • Documentation > fdoc convert spec --output=./html - html .fdoc - endpointrequest
  • - Everything is ne, but! - Scaffold errors will overwrite with an empty fdoc! - Cant allow true/false spec cases.! - zipmark/rspec_api_documentation Documentation - fdoc fdoc .fdoc - token token user - survey gem
  • Documentation - Body request - cURL - DSL specs
  • jbuilder & serializer - rails template - active_model_serializer()
  • jbuilder & serializer # app/models/recipe.rb class Dish < ActiveRecord::Base belongs_to :user end ! # app/models/user.rb class User < ActiveRecord::Base has_many :dishes end ! # app/controllers/dishes_controller.rb class DishesController < ApplicationController def index @dishes = Dish.all end end - model, controller
  • jbuilder & serializer # app/views/dishes/index.json.jbuilder json.dishes dishes do |json, dish| json.id dish.id json.description dish.description json.url dish_url(dish) json.partial! "api/v1/users/user", user: dish.user end ! # app/serializers/dish_serializer.rb class DishSerializer < ActiveModel::Serializer attributes :id, :description, :url has_one :user ! def url dish_url end end - jbuilder key, value - jbuilder partial - jbuilder action *.json.jbuilder - serializer model serializer model - serializer (Object Oriented) - serializer jbuilder
  • jbuilder & serializer # dishes/index.json { dishes: [ { id: 1, description: "dish1", url: "dishes/1", user: { username: "user1" } }, { id: 2, description: "dish2", url: "dishes/2", user: { username: "user1" } } ] } -
  • jbuilder & serializer - jbuilder - serializer - API
  • Duplicate Data - jbuilder & serializer
  • Duplicate Data # dishes/index.json { dishes: [ { id: 1, description: "dish1", url: "dishes/1", user: { username: "user1" } }, { id: 2, description: "dish2", url: "dishes/2", user: { username: "user1" } } ] } - -
  • # app/serializers/base_serializer.rb class BaseSerializer < ActiveModel::Serializer # sideload related data by default embed :ids, include: true end ! # app/serializers/dish_serializer.rb class DishSerializer < BaseSerializer attributes :id, :description, :url has_one :user ! def url dish_url end end Duplicate Data - jbuilder - serializer superclass embed include associations
  • Duplicate Data # dishes/index.json { users: [ { "id": 1, username: "user1" } ], dishes: [ { id: 1, description: "dish1", url: "dishes/1", user_id: 1 }, { id: 2, description: "dish2", url: "dishes/2", user_id: 1 } ] } - users request user - dishes user user_id id - API Users parse - API
  • rack-rewrite - API legacy routes - routes.rb redirect - gem: rack-rewrite
  • rack-rewrite A rack middleware for dening and applying rewrite rules. Rewrite - rewrite web server, e.g. Apache, nginx(engine x) term redirect - rack middleware request rails redirect
  • rack-rewrite # Rewrite legacy routes config.middleware.insert_before(Rack::Runtime, Rack::Rewrite) do r301 '/mobile', 'http://mobile.icook.tw' r301 %r{/recipes?/.+?/dishes/(.*)}, '/dishes/$1' end ! # Better maintainability than nginx rewrite and # better performance than rails routes. - route regular expression(ruby) - nginx rewrite rails routes
  • status code only, if its suitable - - HTTP status code - CREATE DELETE method
  • Secure - API Users
  • Secure - Use User-Agent header! - Constrain your routes, use only and except! - kickstarter/rack-attack! - Rack middleware for blocking & throttling - API User-Agent Server request (DDos) - routes - kickstarter/rack-attack gem rack middleware rails request
  • Neil Gaiman 2012 People will tolerate how unpleasant you are if your work is good and you deliver it on time. ! ! People will forgive the lateness of your work if it is good and they like you. ! ! And you dont have to be as good as everyone else if youre on time and its always a pleasure to hear from you. - Neil Gaiman 2012 University of Art
  • People will tolerate the incomplete document if your API is efcient and consistent. ! People will forgive the inconsistency