WEB API 設計規劃

什麼是API?

API是Application Programming Interface的縮寫,以下是WIKI上對於API的定義:

An application programming interface (API) is a protocol intended to be used as an interface by software components to communicate with each other.

應用程式介面(英語:Application Programming Interface,簡稱:API),又稱為應用編程介面,就是軟體系統不同組成部分銜接的約定。

而透過WEB銜接的API就叫做WEB API了,也是WEB工程師最常需要處理的API,舉凡要連結Facebook、Google乃至內外部系統都會需要用到他們的API,如果自己的系統需要給其他系統使用,也必須提供自己的WEB API。

API的重要性

對於外部系統來說,API就是你的系統的全部,因為外部系統能看到的使用的部分就只有這些。也因此,好的API可以加速系統開發,不好的API也會直接地導致開發進度的拖延。讓我們來看看下面兩個系統的API:

System A

    /users         # 列出所有使用者
    /users/abc     # 抓取使用者abc的資料
    /posts         # 列出所有文章
    /posts/1234    # 抓取文章#1234的內容

System B

    /a             # 列出所有使用者
    /b             # 抓取使用者abc的資料
    /c             # 列出所有文章
    /d             # 抓取文章#1234的內容

雖然這是比較極端的案例,但相信不少人都遇過看都看不懂的API,更何況這還不是最慘的,最慘的是API本身還有誤導的傾向,比如說System B的 /c 改為 /users,你以為可以抓到使用者列表卻回給你文章列表。就算系統再怎樣強大穩定,對外的唯一接口如果設計不當,就有可能大幅提升錯誤的結果。舉個現實生活的例子好了,看看下面這張圖:

門把上寫著推,但門把本身卻是可以拉的設計,那我到底要拉還是要推呢?無論這門本身是推還是拉,在設計上與使用上產生的衝突會造成使用者的困惑,看到push的使用者會推、抓到門把沒看文字的會拉,也許管理者還在納悶為什麼這門一天到晚被用壞。

以上兩個例子其實都是反例,用來解釋介面設計的不好會造成什麼樣的麻煩。再反過來說,設計良好的介面可以讓使用者在不需要太多幫助(說明文字等)的情況下就能輕易地照著設計者規劃的方向使用,想像那個門如果拿掉push這個字,其實大家是不是會很自然的去拉這個門?System A的幾個URL,如果沒有說明文字,其實也大概能猜得出來在幹嘛吧?

有一句話我很喜歡:

Structure causes behavior.

一個網站,如果能好好架構規劃其WEB API,其實是可以大幅影響使用者(開發者)的使用方式的。

下面就列出一些WEB API設計規劃上應該要注意的重點。

物件命名使用名詞 避免動詞

程式裡經典的KISS(Keep It Simple & Stupid)準則也可以用到API設計上,/users是個簡單易懂的API,/getUsers同時也是簡單易懂,那為什麼要避免後者?因為HTTP本身其實就有四種request method可以對應物件的CRUD了(Create: POST, Read: GET, Update: PUT, Delete: DELETE),所以一個簡單的 /users 就可以有四種不同的操作,不需要再另外有一個動詞來讓API名稱變長變成 /getUsers 或是 /createUser 。況且名稱越長,API看起來就越複雜,越複雜的東西就越容易出錯。

如果我們都用名詞來代表每個物件的集合,ID代表個別物件,那麼API就可以精簡成 /物件名/物件ID 的格式,例如:/users/1234

那如果我們需要做更進階的操作呢?比如說,列出所有名稱是A開頭的user?

仔細分析一下,列出使用者這個動作,就是/users,而所有A開頭的user其實就是 GET /users的子集合,因此我們可以加個搜尋的參數,讓API變成 /users?q=key,這樣一來同樣的URL透過不同的參數就有不同的功能,不但簡潔也非常有彈性。

小結一下:


    #基本操作
    GET    /users             #列出所有使用者
    POST   /users             #新增使用者
    PUT    /users             #修改使用者(多個)
    DELETE /users             #刪除所有使用者
    GET    /users/:id         #取得使用者#{id}的資訊
    PUT    /users/:id         #修改使用者#{id}的資訊
    DELETE /users/:id         #刪除使用者#{id}

    #進接操作
    GET    /users?q=key       #列出所有資訊中含有key的使用者

動作使用動詞

有時系統提供的不只是物件的存取,還有一些計算、統計等動作,如搜尋功能,這種功能本身是一種動作,命名自然以動詞為主,而動作的對象就加在query了(這邊剛好跟前段的名詞相反,變成動詞為主,名詞為受體),如/search?q=key可以是搜尋系統中所有含有key的資訊。而通常動詞本身就是一個動作,因此不會再需要區分CRUD的動作,只需要HTTP GET就可以搞定。從名稱可以簡單的判斷出來之外,說明文件也最好把物件跟操作(動詞跟名詞)分開寫,避免混淆。

命名要切題

前面提到要以名詞來命名,但如果一個API裡面充斥著 /objects, /items, /assets, /collections 這類比較籠統的名詞,一樣容易讓開發者搞混,非必要時盡量避免。命名盡量做到一眼就看得懂,一個單字就點出該物件的實際功能,像/users 很容易就猜出是使用者資訊,/checkins是文章資訊等。且由於通常GET /objects的時候取得的是物件的列表,因此多數是以複數形態來命名。至於大寫問題就看各人喜好,重點是不管單複數、大小寫只要統一一致即可。

支援多種格式

API的功用就是給外部系統使用,很多時候會同時有不同的外部系統使用,且會需要各種不同的格式ex: JSON, XML, HTML,先來看看各大網站的API是如何設計的:

Google Data

?alt=json

Foursquare

/venue.json

Digg

Accept: application/json (in HTML request header)
?type=json (URL query)

可以看到做法不盡相同,其中Foursquare的方式最為直覺,就像電腦裡檔案的副檔名一樣,開發者甚至不用記憶要取得不同檔案格式的關鍵字是什麼(alt for google, type for Digg)。

API輸出

至此,我們可以先看一下各種不同的API request & response:

CREATE: 新增一隻咖啡色,名為Al的狗

Request

POST /dogs name=Al&furColor=brown

Response

200 OK { "dog":{ "id": "1234", #創建成功的物件應有一個id, 供後續使用 "name": "Al", "furColor": "brown" } }

UPDATE: 把Al改名為Rover

Request

PUT /dogs/1234 name=Rover

Response

200 OK {"dog":{ "id":"1234", "name": "Rover", "furColor": "brown"}

READ: 取得Rover(id: 1234)的資訊

Request

GET /dogs/1234

Response

200 OK {"dog":{ "id":"1234", "name": "Rover", "furColor": "brown"}

READ: 取得所有狗的資訊

Request

GET /dogs

Response

200 OK {"dogs": [{"dog":{ "id":"1233", "name": "Fido", "furColor": "white"}}, {"dog":{ "id":"1234", "name": "Rover", "furColor": "brown"}}] "_metadata": [{"totalCount":327,"limit":25,"offset":100}] }

DELETE: 刪除Rover

Request

DELETE /dogs/1234

Response

200 OK

錯誤訊息

系統總是會遇到錯誤狀況,一般來說一個API Request會有三種狀況:

  • 成功
  • 系統端錯誤
  • App端(開發者端)錯誤

WEB API既然是以HTTP做溝通的橋梁,那麼使用HTTP Status Code是再自然也不過的事。上述三種狀況分別對應了:

  • 200 - OK
  • 500 - Internal Server Error
  • 400 - Bad Request

當然HTTP Status Code不止這些,就依照系統需要去自訂了,重點就是在讓開發者遇到錯誤的時候可以很快地辨別錯誤發生的原因,以下幾個也是常用到的:

  • 201 - Created
  • 304 - Not Modified
  • 401 - Unauthorized
  • 403 - Forbidden
  • 404 - Not Found

不過當然這些code只能給個方向,如需要更友善的話,就在http response裡面加上更多文字敘述,甚至提供連結至相關幫助頁面,都是對開發者非常有幫助的行為。

{"developerMessage" : "Verbose, plain language description of
the problem for the app developer with hints about how to fix
it.", "userMessage":"Pass this message on to the app user if
needed.", "errorCode" : 12345, "more info":

Final thoughts

API是兩個(以上)系統溝通的界面,設計的好的API,可以大大減低溝通上的成本,因此強烈建議在系統初始規劃的時候就要把API定義清楚,而不是邊做才邊把需要的API一個一個加進來。且定義清楚有個好處,系統兩端的開發者就可以獨立開發,照著API走就是了。在API的設計上當然是越簡單越好,但重點是提供必需的資訊,且資訊要放在該放的欄位,如名詞在URL path、動詞在HTTP method、進階功能在URL query string、額外資訊在http header等。最重要的重點就是:規範儘早定出來,清楚簡單明瞭為原則,這樣無論對系統開發還是API的使用者來說都有很大的幫助。

參考資料

沒有留言:

張貼留言