RESTful API 設計思路及實踐

發表於2016-09-23

記得第一次寫APP的時候,那時還完全不知道REST這個東西,對Web Service也是一知半解。我和另一個同學在討論使用什麼協議來互動時,通過各自充分的調研之後(其實就是搜尋引擎找一找……),一致認為,HTTP這個東西本身就對頻寬的消耗這麼大了,這麼多Web Service(當時還是SOAP當道)還是基於HTTP之上的,這得浪費多少頻寬啊。最後一致決定使用Socket來通訊,現在想想當時也是挺不容易的,我們硬是在Socket上搭了一套通訊協議,還發展到了第二版。

RESTful API 設計思路及實踐

今天在移動應用普及、前後端分離的大浪潮下,RESTful風格的API大行其道,可是因為它本身就是一個比較模糊且寬泛的概念,所以每個人對它的理解都有千差萬別。我覺得我們在技術選型的時候,在自己的技術積累以及參考已有的行業最佳實踐的基礎上,應當首先考慮自身系統的需求,思考「選擇某一種技術」會對系統的開發和維護帶來哪些好處與壞處,而不是人云亦云看著別人用什麼自己就用什麼。而且RESTful API設計目前並沒有一個公認的行業最佳實踐,故而開發者在設計一個API系統時,更應該根據自身的情況量身定製,千萬不要說「我照著某某公司的開放API照搬」就好了。 本文將根據我使用REST的經驗來總結一下RESTful API設計的一些知識和經驗,自勉。本文將不討論Oauth等安全問題。

首先理清一些概念:
  • REST(Representational State Transfer)
    定義了一套基於Web的資料互動方式的設計風格。
  • RESTful
    符合REST風格的API就可以叫做RESTful API。注意,本文講到的RESTful API設計方法將是基於HTTP和JSON實現方式,但不論HTTP還是JSON都不是REST的標準。REST只是風格,沒有標準。
  • 動詞、RPC
    在微信裡搜尋【RESTful API 設計】,出來好多文章都是說怎麼在RESTful Uri裡使用動詞等等,這些人除了一部分是把文章拿來抄一抄的,其他的其實搞混了REST和RPC的概念了,REST強調資源,RPC強調動作,所以REST的Uri組成為名詞,RPC多為動詞短語。然而它們也並不是完全不相關的兩個東西,本文中的實現就會參考一部分JSON-RPC的設計思想。
  • Web Service
    這個是一個更古老的概念,有一套它的理論,不過我更傾向於把它理解成任何基於Web提供的服務。

設計方法及原則:

1. 使用HTTP方法:

HTTP1.1的規範定義了8個動詞,然而HTTP作為一個規範並沒有被嚴格地遵守著,在大多數情況下POST是可以完成除任何種類的請求,所以現在很多的API設計都是隻是用GET和POST來呼叫API,在這種情況下,一般的做法是使用GET用來獲取資源,其他的行為都是用POST來完成,而為了區別不同的行為,往往在API的Uri中加入動詞,如百度推送的如下API:

[ POST ] /rest/3.0/app/del_tag

功能

刪除一個已存在的tag

引數

引數名 型別 必需 限制 描述
tag string 1~128位元組,但不能為‘default’ 標籤名稱

返回值

名稱 型別 描述
tag string 標籤名稱
result number 狀態 0:建立成功; 1:建立

更清晰API設計的可能會使用GET POST PUT DELETE四種方法分別代表“查詢、新增、更新、刪除”等四個動作,這在概念上是符合HTTP規範的,如Google的如下API:

Request

DELETE https://www.googleapis.com/bigquery/v2/projects//datasets/?key={YOUR_API_KEY}

Response

404 Not Found

– Show headers –

Not Found

在我看來,沒有絕對的好與不好。如果使用第一種方法,那麼只要保證Uri的語義清晰,其實和使用第二種方法沒有太大的區別。

2. Uri格式:

Uri在REST中標識了一個資源,但是在具體的API設計中,往往不能做到完全的對於資源的對映,本文中的設計將參考比較流行的Uri設計,大致有這麼幾條:

  • Uri的根(root, /)應當能夠標識這是一個RESTful API,以與同目錄下其他可能存在的資源進行區分。
  • 緊接著Uri的根,應當標識當前API的版本號。
  • 如果方法是POST或者PUT,儘量避免使用URL編碼的引數,儘量保持Uri的乾淨。
  • 如果方法是DELETE,Uri應當完全標識了需要刪除的物件或者物件的集合,避免在DELETE的請求中使用其他引數,因為某些伺服器可能會丟棄伴隨著DELETE傳送的內容。

這裡再次拿行業標杆Google的開放API來舉例:

POST https://www.googleapis.com/books/v1/mylibrary/annotations

PUT https://www.googleapis.com/bigquery/v2/projects/p1/datasets/p2

DELETE https://www.googleapis.com/bigquery/v2/projects/{project-parameter}/datasets/{datasets-parameter}

3. 固定返回碼

REST的大部分實現都是一個基於HTTP的,那麼自然而然就少不了與返回碼打交道,然而不幸的是,HTTP的返回碼定義的看起來十分隨意,很多錯誤資訊語焉不詳,而且在實際的開發中,API的使用者需要處理鏈路的問題(如超時等)、種類繁多的HTTP返回碼、和實際的返回內容,不堪其繁瑣。更嚴重的是,這些返回碼大多最終依賴於服務端開發者的具體實現,而這種看似約定的東西分別在客戶端和服務端開發者眼中的含義可能相去甚遠。

那麼從需求入手,我們在使用RESTful API時需要使用返回碼的原因大致是這樣的:客戶端在呼叫一個API之後,需要在接收到的反饋必須要能夠標識這次呼叫是否成功,如果不成功,客戶端需要拿到失敗的原因。我們可以在API設計時作一個小小的約定,就能完美的滿足以上需求了。

服務端在成功接收到客戶端的請求之後,永遠返回200,具體成功與否及進一步的資訊放入返回的內容。

在這個場景中,如果是鏈路出了問題或者伺服器錯誤等(返回碼不等於200),客戶端很容易就能捕獲這個錯誤,如果鏈路沒問題,那麼出錯與否在獲取到的反饋內容中會有詳細的描述。

4. 固定返回結構

現在越來越多的API設計會使用JSON來傳遞資料,本文中的設計也將使用JSON。JSON-RPC是一個基於JSON的廣為人知的設計簡潔的RPC規範,本文將借鑑JSON-RPC的響應物件的設計。

JSON-RPC中服務端響應物件的設計的基本理念是,只要呼叫成功,服務端必須響應資料(如在#3中討論的那樣),而響應資料的格式在任何情況下都應當是一致的,JSON-RPC的響應格式是這麼設計的:

jsonrpc

A String specifying the version of the JSON-RPC protocol. MUST be exactly “2.0”.

result

This member is REQUIRED on success.

This member MUST NOT exist if there was an error invoking the method.

The value of this member is determined by the method invoked on the Server.

error

This member is REQUIRED on error.
This member MUST NOT exist if there was no error triggered during invocation.

The value for this member MUST be an Object as defined in section 5.1.

id

This member is REQUIRED.

It MUST be the same as the value of the id member in the Request Object.

If there was an error in detecting the id in the Request object (e.g. Parse error/Invalid Request), it MUST be Null.

由於JSON-RPC的目標是建立一個通用的規範,所以響應格式的設計還是有些複雜,我們可以只取其中它對於error物件的設計,所有返回的格式必須是這樣的:

這種格式的設計在許多大公司的開放API中也較為常見,比如作為行業標杆的Google,在呼叫Google開放平臺的某API後獲取到的錯誤資料如下,其設計思想與本文討論的這種返回格式的思想如出一轍。

綜上所述,本文所探討的API設計是這樣的:
  1. 所有API的Uri為基於HTTP的名詞性短語,用來代表一種資源。
  2. Uri格式如文中所述。
  3. 使用GET POST PUT DELETE四種方法分別代表對資源的“查詢、新增、更新、刪除”。
  4. 服務端接收到客戶端的請求之後,統一返回200,如果客戶端獲取到的返回碼不是200,代表鏈路上某一個環節出了問題。
  5. 服務端所有的響應格式為:

    他們的含義分別代表:
    • code為0代表呼叫成功,其他會自定義的錯誤碼;
    • message表示在API呼叫失敗的情況下詳細的錯誤資訊,這個資訊可以由客戶端直接呈現給使用者,否則為空;
    • data表示服務端返回的資料,具體格式由服務端自定義,API呼叫錯誤為空

相關文章