SlideShare a Scribd company logo
1 of 40
Download to read offline
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 愛料理
We’re 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
Efficient
From routes, request & response data, template…
Automatic generated, updated, understandable…
Fast, as small as we requested…
- 設計 API 的有三個重要原則分別是:C、D、E
- 一致性包含 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
- 分成 request、response、responseCodes 等區塊
Documentation
> fdoc convert spec --output=./html
- 一樣透過內建指令產生 html,而這就是根據 .fdoc 產生
- 有 endpoint、request 應該要有的欄位、格式、必須欄位等等
- Everything is fine, but…!
- Scaffold errors will overwrite with an empty fdoc!
- Can’t 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 defining 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 it’s 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 don’t have to be as good as everyone
else if you’re on time and it’s always a pleasure to
hear from you.”
- 最後引用一段我滿喜歡的演講,來自於 Neil Gaiman 2012 在 University of Art 的演講
“People will tolerate the incomplete document if
your API is efficient and consistent.
!
People will forgive the inconsistency of your API
if it is efficient and the document is fine.
!
And your API doesn’t have to be as efficient as
everyone else if it’s consistent and it’s always a
pleasure to read the documents.”
– David Yun 2014
- 改寫成 API 的三個原則也滿適用的。
Q & A

More Related Content

Viewers also liked

Anteprima modulo 7
Anteprima modulo 7Anteprima modulo 7
Anteprima modulo 7eAgisco
 
150525 教育評価論 第6講
150525 教育評価論 第6講150525 教育評価論 第6講
150525 教育評価論 第6講Koyo Yamamori
 
Modelo Portafolio
Modelo PortafolioModelo Portafolio
Modelo PortafolioMiriam Vega
 
Anteprima modulo 5
Anteprima modulo 5Anteprima modulo 5
Anteprima modulo 5eAgisco
 
For chiropractic to progress it no longer needs Philosophy?
For chiropractic to progress it no longer needs Philosophy?For chiropractic to progress it no longer needs Philosophy?
For chiropractic to progress it no longer needs Philosophy?Braddon Atkinson
 
Arcade carestia acquisition 2013
Arcade   carestia acquisition 2013Arcade   carestia acquisition 2013
Arcade carestia acquisition 2013Arcade Marketing
 
Play2+Scala事始め
Play2+Scala事始めPlay2+Scala事始め
Play2+Scala事始めAsami Abe
 
Nahdet El Mahrousa's Incubator Information session
Nahdet El Mahrousa's Incubator Information session Nahdet El Mahrousa's Incubator Information session
Nahdet El Mahrousa's Incubator Information session Nahdet El Mahrousa
 
Anteprima modulo 3
Anteprima modulo 3Anteprima modulo 3
Anteprima modulo 3eAgisco
 
Cách mới để món cá sốt cà ri ngon bất ngờ
Cách mới để món cá sốt cà ri ngon bất ngờCách mới để món cá sốt cà ri ngon bất ngờ
Cách mới để món cá sốt cà ri ngon bất ngờCác Món Ăn Ngon
 
Medyo final patho slide orientation
Medyo final patho slide orientationMedyo final patho slide orientation
Medyo final patho slide orientationJoriz Abel
 
セッションA 4「x pages の落とし穴? xpages traps and pitfalls」(20131106)公開用
セッションA 4「x pages の落とし穴? xpages traps and pitfalls」(20131106)公開用 セッションA 4「x pages の落とし穴? xpages traps and pitfalls」(20131106)公開用
セッションA 4「x pages の落とし穴? xpages traps and pitfalls」(20131106)公開用 Koichi Hatanaka
 
Mike baker ovarb, fidelidad
Mike baker ovarb, fidelidadMike baker ovarb, fidelidad
Mike baker ovarb, fidelidadMike Baker Ovarb
 
W.A.Y. Where are you? Strumento di a
W.A.Y. Where are you? Strumento di aW.A.Y. Where are you? Strumento di a
W.A.Y. Where are you? Strumento di aeAgisco
 

Viewers also liked (18)

Anteprima modulo 7
Anteprima modulo 7Anteprima modulo 7
Anteprima modulo 7
 
150525 教育評価論 第6講
150525 教育評価論 第6講150525 教育評価論 第6講
150525 教育評価論 第6講
 
Modelo Portafolio
Modelo PortafolioModelo Portafolio
Modelo Portafolio
 
Anteprima modulo 5
Anteprima modulo 5Anteprima modulo 5
Anteprima modulo 5
 
For chiropractic to progress it no longer needs Philosophy?
For chiropractic to progress it no longer needs Philosophy?For chiropractic to progress it no longer needs Philosophy?
For chiropractic to progress it no longer needs Philosophy?
 
Mike baker eclipse
Mike baker eclipseMike baker eclipse
Mike baker eclipse
 
Arcade carestia acquisition 2013
Arcade   carestia acquisition 2013Arcade   carestia acquisition 2013
Arcade carestia acquisition 2013
 
Play2+Scala事始め
Play2+Scala事始めPlay2+Scala事始め
Play2+Scala事始め
 
Fizyka
FizykaFizyka
Fizyka
 
Nahdet El Mahrousa's Incubator Information session
Nahdet El Mahrousa's Incubator Information session Nahdet El Mahrousa's Incubator Information session
Nahdet El Mahrousa's Incubator Information session
 
Anteprima modulo 3
Anteprima modulo 3Anteprima modulo 3
Anteprima modulo 3
 
Cách mới để món cá sốt cà ri ngon bất ngờ
Cách mới để món cá sốt cà ri ngon bất ngờCách mới để món cá sốt cà ri ngon bất ngờ
Cách mới để món cá sốt cà ri ngon bất ngờ
 
Gesture recognition1
Gesture recognition1Gesture recognition1
Gesture recognition1
 
Medyo final patho slide orientation
Medyo final patho slide orientationMedyo final patho slide orientation
Medyo final patho slide orientation
 
Introduction law III Class
Introduction law III ClassIntroduction law III Class
Introduction law III Class
 
セッションA 4「x pages の落とし穴? xpages traps and pitfalls」(20131106)公開用
セッションA 4「x pages の落とし穴? xpages traps and pitfalls」(20131106)公開用 セッションA 4「x pages の落とし穴? xpages traps and pitfalls」(20131106)公開用
セッションA 4「x pages の落とし穴? xpages traps and pitfalls」(20131106)公開用
 
Mike baker ovarb, fidelidad
Mike baker ovarb, fidelidadMike baker ovarb, fidelidad
Mike baker ovarb, fidelidad
 
W.A.Y. Where are you? Strumento di a
W.A.Y. Where are you? Strumento di aW.A.Y. Where are you? Strumento di a
W.A.Y. Where are you? Strumento di a
 

Similar to 10 Things to Make API Users Like You

Rest Ruby On Rails
Rest Ruby On RailsRest Ruby On Rails
Rest Ruby On Railsshaokun
 
Mopcon2014 - 使用 Sinatra 結合 Ruby on Rails 輕鬆打造完整 Full Stack 網站加 API Service服務
Mopcon2014 - 使用 Sinatra 結合 Ruby on Rails 輕鬆打造完整 Full Stack 網站加 API Service服務Mopcon2014 - 使用 Sinatra 結合 Ruby on Rails 輕鬆打造完整 Full Stack 網站加 API Service服務
Mopcon2014 - 使用 Sinatra 結合 Ruby on Rails 輕鬆打造完整 Full Stack 網站加 API Service服務Mu-Fan Teng
 
Ruby rails分享
Ruby rails分享Ruby rails分享
Ruby rails分享Cam Song
 
API Survey #2 - Firebase realtime database
API Survey #2 - Firebase realtime databaseAPI Survey #2 - Firebase realtime database
API Survey #2 - Firebase realtime databaseSzuping Wang
 
J Ruby和Rails 让Ruby语言融入Java项目
J Ruby和Rails 让Ruby语言融入Java项目J Ruby和Rails 让Ruby语言融入Java项目
J Ruby和Rails 让Ruby语言融入Java项目George Ang
 
Java Api Design
Java Api DesignJava Api Design
Java Api Designyiditushe
 
用 Ruby 開發 IoT 應用 - 以 RubyConf.tw 打卡系統為例
用 Ruby 開發 IoT 應用 - 以 RubyConf.tw 打卡系統為例用 Ruby 開發 IoT 應用 - 以 RubyConf.tw 打卡系統為例
用 Ruby 開發 IoT 應用 - 以 RubyConf.tw 打卡系統為例亮齊 曾
 
D2_Node在淘宝的应用实践
D2_Node在淘宝的应用实践D2_Node在淘宝的应用实践
D2_Node在淘宝的应用实践Jackson Tian
 
用JAX-RS和Jersey完成RESTful Web Services
用JAX-RS和Jersey完成RESTful Web Services用JAX-RS和Jersey完成RESTful Web Services
用JAX-RS和Jersey完成RESTful Web Servicesjavatwo2011
 
JSDC 2015 React-Native 實戰
JSDC 2015 React-Native 實戰JSDC 2015 React-Native 實戰
JSDC 2015 React-Native 實戰Sam Lee
 
rebar erlang 2
rebar erlang 2rebar erlang 2
rebar erlang 2致远 郑
 
2012 java two-desktop-appliction-using-j-ruby-with-swt
2012 java two-desktop-appliction-using-j-ruby-with-swt2012 java two-desktop-appliction-using-j-ruby-with-swt
2012 java two-desktop-appliction-using-j-ruby-with-swttka
 
容器驅動開發 - .NET Conf 2017 @ 台中
容器驅動開發 - .NET Conf 2017 @ 台中容器驅動開發 - .NET Conf 2017 @ 台中
容器驅動開發 - .NET Conf 2017 @ 台中Andrew Wu
 
新浪云计算公开课第二期:Sae平台的灵活应用(吕毅、魏世江)
新浪云计算公开课第二期:Sae平台的灵活应用(吕毅、魏世江)新浪云计算公开课第二期:Sae平台的灵活应用(吕毅、魏世江)
新浪云计算公开课第二期:Sae平台的灵活应用(吕毅、魏世江)锐 张
 
一拍一产品背后的故事(React实战)
一拍一产品背后的故事(React实战)一拍一产品背后的故事(React实战)
一拍一产品背后的故事(React实战)Kejun Zhang
 
Uliweb框架思想与编程
Uliweb框架思想与编程Uliweb框架思想与编程
Uliweb框架思想与编程modou li
 

Similar to 10 Things to Make API Users Like You (20)

Rest Ruby On Rails
Rest Ruby On RailsRest Ruby On Rails
Rest Ruby On Rails
 
Mopcon2014 - 使用 Sinatra 結合 Ruby on Rails 輕鬆打造完整 Full Stack 網站加 API Service服務
Mopcon2014 - 使用 Sinatra 結合 Ruby on Rails 輕鬆打造完整 Full Stack 網站加 API Service服務Mopcon2014 - 使用 Sinatra 結合 Ruby on Rails 輕鬆打造完整 Full Stack 網站加 API Service服務
Mopcon2014 - 使用 Sinatra 結合 Ruby on Rails 輕鬆打造完整 Full Stack 網站加 API Service服務
 
Ruby rails分享
Ruby rails分享Ruby rails分享
Ruby rails分享
 
API Survey #2 - Firebase realtime database
API Survey #2 - Firebase realtime databaseAPI Survey #2 - Firebase realtime database
API Survey #2 - Firebase realtime database
 
使用 Controller
使用 Controller使用 Controller
使用 Controller
 
J Ruby和Rails 让Ruby语言融入Java项目
J Ruby和Rails 让Ruby语言融入Java项目J Ruby和Rails 让Ruby语言融入Java项目
J Ruby和Rails 让Ruby语言融入Java项目
 
Java Api Design
Java Api DesignJava Api Design
Java Api Design
 
用 Ruby 開發 IoT 應用 - 以 RubyConf.tw 打卡系統為例
用 Ruby 開發 IoT 應用 - 以 RubyConf.tw 打卡系統為例用 Ruby 開發 IoT 應用 - 以 RubyConf.tw 打卡系統為例
用 Ruby 開發 IoT 應用 - 以 RubyConf.tw 打卡系統為例
 
D2_Node在淘宝的应用实践
D2_Node在淘宝的应用实践D2_Node在淘宝的应用实践
D2_Node在淘宝的应用实践
 
用JAX-RS和Jersey完成RESTful Web Services
用JAX-RS和Jersey完成RESTful Web Services用JAX-RS和Jersey完成RESTful Web Services
用JAX-RS和Jersey完成RESTful Web Services
 
JSDC 2015 React-Native 實戰
JSDC 2015 React-Native 實戰JSDC 2015 React-Native 實戰
JSDC 2015 React-Native 實戰
 
使用 Controller
使用 Controller使用 Controller
使用 Controller
 
rebar erlang 2
rebar erlang 2rebar erlang 2
rebar erlang 2
 
2012 java two-desktop-appliction-using-j-ruby-with-swt
2012 java two-desktop-appliction-using-j-ruby-with-swt2012 java two-desktop-appliction-using-j-ruby-with-swt
2012 java two-desktop-appliction-using-j-ruby-with-swt
 
容器驅動開發 - .NET Conf 2017 @ 台中
容器驅動開發 - .NET Conf 2017 @ 台中容器驅動開發 - .NET Conf 2017 @ 台中
容器驅動開發 - .NET Conf 2017 @ 台中
 
新浪云计算公开课第二期:Sae平台的灵活应用(吕毅、魏世江)
新浪云计算公开课第二期:Sae平台的灵活应用(吕毅、魏世江)新浪云计算公开课第二期:Sae平台的灵活应用(吕毅、魏世江)
新浪云计算公开课第二期:Sae平台的灵活应用(吕毅、魏世江)
 
一拍一产品背后的故事(React实战)
一拍一产品背后的故事(React实战)一拍一产品背后的故事(React实战)
一拍一产品背后的故事(React实战)
 
Jsp讲义
Jsp讲义Jsp讲义
Jsp讲义
 
Uliweb框架思想与编程
Uliweb框架思想与编程Uliweb框架思想与编程
Uliweb框架思想与编程
 
RESTful
RESTfulRESTful
RESTful
 

10 Things to Make API Users Like You

  • 1. 10 Things to Make API Users Like You @abookyun - 我是 David。今天分享的主題是讓 API 使用者喜歡你的 10 件事情。
  • 2. iCook 愛料理 The largest recipes sharing website in Taiwan. - 2012 年加入 Polydice,是 Backend Engineer - iCook 是 Polydice 的主要專案,是台灣最大的食譜分享社群網站。 - iCook 是用 Ruby on Rails 設計開發的 - 每個月超過兩百萬人次在上面分享與尋找自己喜歡的食譜。
  • 3. iCook 愛料理 We’re ready for! iOS, Android and Windows - iCook 目前在各大平台上都已經有 app,近期會有 Windows 8 app - app 與 server 透過 API 溝通,所以今天就是分享在開發 API 的經驗
  • 4. 40,000 recipes 600,000 members 1,000,000 downloads 1,600,000 API calls per day - 相關數據,其中 API calls 約為 160 萬次每日的量
  • 6. Principles Documentation Consistent Efficient From routes, request & response data, template… Automatic generated, updated, understandable… Fast, as small as we requested… - 設計 API 的有三個重要原則分別是:C、D、E - 一致性包含 routes 的規則、request & response 的資料內容一致性 - 一份完整易懂與隨時更新的文件對 API Users 是重要的敲門磚 - 高效率的 API 擁有很低的 response time、最少的 requests 等特性 - 接下來的幾個案例分享都會圍繞在這幾個重要原則上
  • 7. Diary 料理⽇日記 - Diary 料理日記是近期的新產品,隨手做的料理輕鬆拍照上傳分享 - 接下來的案例會用此 API 做說明。料理 = dish、留言 = comment、使用者 = user
  • 8. RESTful and Reasonable routes - RESTful 是近年網站開發的重要哲學 - Rails 也是以這樣的哲學做出來的 Framework,已內建 RESTful - routes 除了 RESTful 以外,Reasonable 也是很重要的
  • 9. 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 就可以刪除了
  • 10. 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 來說更簡單容易理解。
  • 11. Implicit in routes - RESTful 的一個重要關鍵就是他暗示了所有 route 都是一個 resource - 這樣的暗示就是一種 API 開發者與 Users 之間的共識,需要更精確的暗示 API Users,來保持 API 的一致性
  • 12. 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 來查詢/修改設定的可能,應該要被修改成下方的形式
  • 13. Debate on page vs. offset - 曾經與 API Users 討論在翻頁的 API 應該要用哪一套 - 大部分情況下這只是習慣上的選擇,但以下有個 edge case
  • 14. Debate on page vs. offset }{ page: 2, per_page: 2 }! { offset: 2, limit: 2 } 頭推 :) 超好吃 :) 不好吃阿!:( 樓上決⾾鬥阿!:@ 떡볶이~敲好吃 1 2 3 4 5 떡볶이~敲好吃 - 料理有五個留言,一般讀取兩者都沒有差別
  • 15. 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
  • 16. Documentation - 一份完整容易閱讀的文件當然對 API Users 是相當重要的敲門磚 - 接下來分享我們用什麼方式處理文件的問題
  • 17. - 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
  • 18. 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(呀磨) 檔案
  • 19. 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 - 分成 request、response、responseCodes 等區塊
  • 20. Documentation > fdoc convert spec --output=./html - 一樣透過內建指令產生 html,而這就是根據 .fdoc 產生 - 有 endpoint、request 應該要有的欄位、格式、必須欄位等等
  • 21. - Everything is fine, but…! - Scaffold errors will overwrite with an empty fdoc! - Can’t allow true/false spec cases.! - zipmark/rspec_api_documentation Documentation - 在產生 fdoc 的指令遇到錯誤會覆寫成空白 fdoc 的問題,所以現在我們自己複製貼上 .fdoc,比較少出錯 - 不能寫正反兩面測試。例如有帶 token 與沒帶 token 的 user 測試不能 - 目前有在 survey 另外一套 gem
  • 22. Documentation - 亮點一:Body 有 request 的「結構」 - 亮點二:cURL 有可直接使用的範例,相當直覺 - 但是,有另外的 DSL,這樣表示我們要重寫全部 specs
  • 23. jbuilder & serializer - rails 知名的 template 使用心得 - 是 active_model_serializer(名稱太長沒有用)
  • 24. 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
  • 25. 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
  • 26. 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" } } ] } - 當然兩個可以產生一樣的結果
  • 27. jbuilder & serializer - jbuilder 等同於樂高積木 - serializer 等同於俄羅斯娃娃 - API 設計是取捨的問題
  • 28. Duplicate Data - jbuilder & serializer 有個延伸問題就是重複的資料
  • 29. 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" } } ] } - 同一個人的兩道不同料理的資料內容 - 資料可大可小,不巧的話會造成多餘的浪費
  • 30. # 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 進來
  • 31. 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 設計終究是取捨的問題
  • 32. rack-rewrite - API 開發時程長了之後可能會有許多 legacy routes - 一般來說會在 routes.rb 裡面 redirect,但可以有更有效率的方法 - gem: rack-rewrite
  • 33. rack-rewrite A rack middleware for defining and applying rewrite rules. Rewrite - rewrite 是 web server, e.g. Apache, nginx(engine x) 的 term,也就是 redirect 的意思 - 他是一個 rack middleware,所以可以提前處理要轉向的 request,不用進到 rails,可以省掉很多時間,像是 redirect 不需要驗證身分就是很好的例子
  • 34. 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 還要好很多
  • 35. status code only, if it’s suitable - 這是一個通則,就是盡量只傳必要的資料就好 - 而如果目前已經有豐富定義的 HTTP status code 能夠解決的事情就這樣吧 - 目前最好的例子就是 CREATE 跟 DELETE 的 method
  • 36. Secure - 一般來說 API Users 不需要知道這些,但還是提一下
  • 37. 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
  • 38. – 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 don’t have to be as good as everyone else if you’re on time and it’s always a pleasure to hear from you.” - 最後引用一段我滿喜歡的演講,來自於 Neil Gaiman 2012 在 University of Art 的演講
  • 39. “People will tolerate the incomplete document if your API is efficient and consistent. ! People will forgive the inconsistency of your API if it is efficient and the document is fine. ! And your API doesn’t have to be as efficient as everyone else if it’s consistent and it’s always a pleasure to read the documents.” – David Yun 2014 - 改寫成 API 的三個原則也滿適用的。
  • 40. Q & A