40
10 Things to Make API Users Like You @abookyun - 我是 David。今天分享的主題是讓 API 使用者喜歡你的 10 件事情。

10 Things to Make API Users Like You

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.

Citation preview

Page 1: 10 Things to Make API Users Like You

10 Things to Make API Users Like You

@abookyun

- 我是 David。今天分享的主題是讓 API 使用者喜歡你的 10 件事情。

Page 2: 10 Things to Make API Users Like You

iCook 愛料理

The largest recipes sharing website in Taiwan.

- 2012 年加入 Polydice,是 Backend Engineer - iCook 是 Polydice 的主要專案,是台灣最大的食譜分享社群網站。 - iCook 是用 Ruby on Rails 設計開發的 - 每個月超過兩百萬人次在上面分享與尋找自己喜歡的食譜。

Page 3: 10 Things to Make API Users Like You

iCook 愛料理

We’re ready for!iOS, Android and Windows

- iCook 目前在各大平台上都已經有 app,近期會有 Windows 8 app - app 與 server 透過 API 溝通,所以今天就是分享在開發 API 的經驗

Page 4: 10 Things to Make API Users Like You

40,000 recipes600,000 members

1,000,000 downloads1,600,000 API calls per day

- 相關數據,其中 API calls 約為 160 萬次每日的量

Page 5: 10 Things to Make API Users Like You

Principles

Page 6: 10 Things to Make API Users Like You

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 等特性 - 接下來的幾個案例分享都會圍繞在這幾個重要原則上

Page 7: 10 Things to Make API Users Like You

Diary 料理⽇日記

- Diary 料理日記是近期的新產品,隨手做的料理輕鬆拍照上傳分享 - 接下來的案例會用此 API 做說明。料理 = dish、留言 = comment、使用者 = user

Page 8: 10 Things to Make API Users Like You

RESTful and Reasonable routes

- RESTful 是近年網站開發的重要哲學 - Rails 也是以這樣的哲學做出來的 Framework,已內建 RESTful - routes 除了 RESTful 以外,Reasonable 也是很重要的

Page 9: 10 Things to Make API Users Like You

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 就可以刪除了

Page 10: 10 Things to Make API Users Like You

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 來說更簡單容易理解。

Page 11: 10 Things to Make API Users Like You

Implicit in routes

- RESTful 的一個重要關鍵就是他暗示了所有 route 都是一個 resource - 這樣的暗示就是一種 API 開發者與 Users 之間的共識,需要更精確的暗示 API Users,來保持 API 的一致性

Page 12: 10 Things to Make API Users Like You

Implicit in routesresources :users do resources :settings, to: "users/settings" end !

# GET /users/username/settings # PUT /users/username/settings !

resources :settings !

# GET /settings # PUT /settings

- 上方的 routes 其實會有暗示可以帶入別人的 username 來查詢/修改設定的可能,應該要被修改成下方的形式

Page 13: 10 Things to Make API Users Like You

Debate on page vs. offset

- 曾經與 API Users 討論在翻頁的 API 應該要用哪一套 - 大部分情況下這只是習慣上的選擇,但以下有個 edge case

Page 14: 10 Things to Make API Users Like You

Debate on page vs. offset

}{ page: 2, per_page: 2 }!{ offset: 2, limit: 2 }

頭推 :)

超好吃 :)

不好吃阿!:(

樓上決⾾鬥阿!:@

떡볶이∼敲好吃

1

2

3

4

5

떡볶이∼敲好吃

- 料理有五個留言,一般讀取兩者都沒有差別

Page 15: 10 Things to Make API Users Like You

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

Page 16: 10 Things to Make API Users Like You

Documentation

- 一份完整容易閱讀的文件當然對 API Users 是相當重要的敲門磚 - 接下來分享我們用什麼方式處理文件的問題

Page 17: 10 Things to Make API Users Like You

- 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

Page 18: 10 Things to Make API Users Like You

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(呀磨) 檔案

Page 19: 10 Things to Make API Users Like You

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 等區塊

Page 20: 10 Things to Make API Users Like You

Documentation> fdoc convert spec --output=./html

- 一樣透過內建指令產生 html,而這就是根據 .fdoc 產生 - 有 endpoint、request 應該要有的欄位、格式、必須欄位等等

Page 21: 10 Things to Make API Users Like You

- 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

Page 22: 10 Things to Make API Users Like You

Documentation

- 亮點一:Body 有 request 的「結構」 - 亮點二:cURL 有可直接使用的範例,相當直覺 - 但是,有另外的 DSL,這樣表示我們要重寫全部 specs

Page 23: 10 Things to Make API Users Like You

jbuilder & serializer

- rails 知名的 template 使用心得 - 是 active_model_serializer(名稱太長沒有用)

Page 24: 10 Things to Make API Users Like You

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

Page 25: 10 Things to Make API Users Like You

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

Page 26: 10 Things to Make API Users Like You

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" } } ] }

- 當然兩個可以產生一樣的結果

Page 27: 10 Things to Make API Users Like You

jbuilder & serializer

- jbuilder 等同於樂高積木 - serializer 等同於俄羅斯娃娃 - API 設計是取捨的問題

Page 28: 10 Things to Make API Users Like You

Duplicate Data

- jbuilder & serializer 有個延伸問題就是重複的資料

Page 29: 10 Things to Make API Users Like You

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" } } ] }

- 同一個人的兩道不同料理的資料內容 - 資料可大可小,不巧的話會造成多餘的浪費

Page 30: 10 Things to Make API Users Like You

# 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 進來

Page 31: 10 Things to Make API Users Like You

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 設計終究是取捨的問題

Page 32: 10 Things to Make API Users Like You

rack-rewrite

- API 開發時程長了之後可能會有許多 legacy routes - 一般來說會在 routes.rb 裡面 redirect,但可以有更有效率的方法 - gem: rack-rewrite

Page 33: 10 Things to Make API Users Like You

rack-rewriteA 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 不需要驗證身分就是很好的例子

Page 34: 10 Things to Make API Users Like You

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 還要好很多

Page 35: 10 Things to Make API Users Like You

status code only, if it’s suitable

- 這是一個通則,就是盡量只傳必要的資料就好 - 而如果目前已經有豐富定義的 HTTP status code 能夠解決的事情就這樣吧 - 目前最好的例子就是 CREATE 跟 DELETE 的 method

Page 36: 10 Things to Make API Users Like You

Secure

- 一般來說 API Users 不需要知道這些,但還是提一下

Page 37: 10 Things to Make API Users Like You

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

Page 38: 10 Things to Make API Users Like You

– 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 的演講

Page 39: 10 Things to Make API Users Like You

“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 的三個原則也滿適用的。

Page 40: 10 Things to Make API Users Like You

Q & A