10 Things to Make API Users Like You

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

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

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