天天看點

Restful Web Service設計規範這些問題你都遇到過嗎?每個資源使用兩個URL用名詞代替動詞表示資源用HTTP方法操作資源對資源集合的URL使用POST方法,建立新資源對具體資源的URL使用PUT方法,來更新資源使用HTTP狀态碼使用小駝峰命名法提供分頁資訊非資源請求用動詞考慮特定資源搜尋和跨資源搜尋

這些問題你都遇到過嗎?

  • 項目資源的URL應該如何設計?
  • 用名詞複數還是用名詞單數?
  • 一個資源需要多少個URL?
  • 用哪種HTTP方法來建立一個新的資源?
  • 可選參數應該放在哪裡?
  • 那些不涉及資源操作的URL呢?
  • 實作分頁和版本控制的最好方法是什麼?

本文來探索最佳實踐方案。

每個資源使用兩個URL

資源集合用一個URL,具體某個資源用一個URL:

/employees         #資源集合的URL
/employees/56      #具體某個資源的URL      

用名詞代替動詞表示資源

這讓你的API更簡潔,URL數目更少。不要這麼設計:

/getAllEmployees

/getAllExternalEmployees

/createEmployee

/updateEmployee

更好的設計:

GET /employees

GET /employees?state=external

POST /employees

PUT /employees/56

Restful Web Service設計規範這些問題你都遇到過嗎?每個資源使用兩個URL用名詞代替動詞表示資源用HTTP方法操作資源對資源集合的URL使用POST方法,建立新資源對具體資源的URL使用PUT方法,來更新資源使用HTTP狀态碼使用小駝峰命名法提供分頁資訊非資源請求用動詞考慮特定資源搜尋和跨資源搜尋

用HTTP方法操作資源

使用URL指定你要用的資源。使用HTTP方法來指定怎麼處理這個資源。使用四種HTTP方法POST,GET,PUT,DELETE可以提供CRUD功能(建立,擷取,更新,删除)。

擷取:使用GET方法擷取資源。GET請求從不改變資源的狀态。無副作用。GET方法是幂等的。GET方法具有隻讀的含義。是以,你可以完美的使用緩存。

建立:使用POST建立新的資源。

更新:使用PUT更新現有資源。

删除:使用DELETE删除現有資源。

2個URL乘以4個HTTP方法就是一組很好的功能。看看這個表格:

POST(建立) GET(讀取) PUT(更新) DELETE(删除)

/employees 建立一個新員工 列出所有員工 批量更新員工資訊 删除所有員工

/employees/56 (錯誤) 擷取56号員工的資訊 更新56号員工的資訊 删除56号員工

對資源集合的URL使用POST方法,建立新資源

建立一個新資源的時,用戶端與伺服器是怎麼互動的呢?

在資源集合URL上使用POST來建立新的資源過程:

Restful Web Service設計規範這些問題你都遇到過嗎?每個資源使用兩個URL用名詞代替動詞表示資源用HTTP方法操作資源對資源集合的URL使用POST方法,建立新資源對具體資源的URL使用PUT方法,來更新資源使用HTTP狀态碼使用小駝峰命名法提供分頁資訊非資源請求用動詞考慮特定資源搜尋和跨資源搜尋

用戶端向資源集合URL/employees發送POST請求。HTTP body 包含新資源的屬性 “Albert Stark”。

RESTful Web伺服器為新員工生成ID,在其内部模型中建立員工,并向用戶端發送響應。這個響應的HTTP頭部包含一個Location字段,訓示建立資源可通路的URL。

對具體資源的URL使用PUT方法,來更新資源

使用PUT更新已有資源:

Restful Web Service設計規範這些問題你都遇到過嗎?每個資源使用兩個URL用名詞代替動詞表示資源用HTTP方法操作資源對資源集合的URL使用POST方法,建立新資源對具體資源的URL使用PUT方法,來更新資源使用HTTP狀态碼使用小駝峰命名法提供分頁資訊非資源請求用動詞考慮特定資源搜尋和跨資源搜尋

用戶端向具體資源的URL發送PUT請求/employee/21。請求的HTTP body中包含要更新的屬性值(21号員工的新名稱“Bruce Wayne”)。

REST伺服器更新ID為21的員工名稱,并使用HTTP狀态碼200表示更改成功。

推薦用複數名詞

推薦:

/employees

/employees/21

不推薦:

/employee

/employee/21

事實上,這是個人愛好問題,但複數形式更為常見。此外,在資源集合URL上用GET方法,它更直覺,特别是GET /employees?state=external、POST /employees、PUT /employees/56。但最重要的是:避免複數和單數名詞混合使用,這顯得非常混亂且容易出錯。

對可選的、複雜的參數,使用查詢字元串(?)。

不推薦做法:

GET /externalEmployees

GET /internalEmployees

GET /internalAndSeniorEmployees

為了讓你的URL更小、更簡潔。為資源設定一個基本URL,将可選的、複雜的參數用查詢字元串表示。

GET /employees?state=internal&maturity=senior

使用HTTP狀态碼

RESTful Web服務應使用合适的HTTP狀态碼來響應用戶端請求

2xx - 成功 - 一切都很好

4xx - 用戶端錯誤 - 如果用戶端發生錯誤(例如用戶端發送無效請求或未被授權)

5xx – 伺服器錯誤 - 如果伺服器發生錯誤(例如,嘗試處理請求時出錯)

參考維基百科上的HTTP狀态代碼。但是,其中的大部分HTTP狀态碼都不會被用到,隻會用其中的一小部分。通常會用到一下幾個:

2xx:成功 3xx:重定向 4xx:用戶端錯誤 5xx:伺服器錯誤

200 成功 301 永久重定向 400 錯誤請求 500 内部伺服器錯誤

201 建立 304 資源未修改 401未授權

403 禁止

404 未找到

傳回有用的錯誤提示

除了合适的狀态碼之外,還應該在HTTP響應正文中提供有用的錯誤提示和詳細的描述。這是一個例子。請求:

GET /employees?state=super

響應:

// 400 Bad Request
{
    "message": "You submitted an invalid state. Valid state values are 'internal' or 'external'",
    "errorCode": 352,
    "additionalInformation" : 
    "http://www.domain.com/rest/errorcode/352"
}      

使用小駝峰命名法

使用小駝峰命名法作為屬性辨別符。

{ “yearOfBirth”: 1982 }

不要使用下劃線(year_of_birth)或大駝峰命名法(YearOfBirth)。通常,RESTful Web服務将被JavaScript編寫的用戶端使用。用戶端會将JSON響應轉換為JavaScript對象(通過調用var person = JSON.parse(response)),然後調用其屬性。是以,最好遵循JavaScript代碼通用規範。

對比:

person.year_of_birth // 不推薦,違反JavaScript代碼通用規範

person.YearOfBirth // 不推薦,JavaScript構造方法命名

person.yearOfBirth // 推薦

在URL中強制加入版本号

從始至終,都使用版本号釋出您的RESTful API。将版本号放在URL中以是必需的。如果您有不相容和破壞性的更改,版本号将讓你能更容易的釋出API。釋出新API時,隻需在增加版本号中的數字。這樣的話,用戶端可以自如的遷移到新API,不會因調用完全不同的新API而陷入困境。 使用直覺的 “v” 字首來表示後面的數字是版本号。

/v1/employees

你不需要使用次級版本号(“v1.2”),因為你不應該頻繁的去釋出API版本。

提供分頁資訊

一次性傳回資料庫所有資源不是一個好主意。是以,需要提供分頁機制。通常使用資料庫中衆所周知的參數offset和limit。

/employees?offset=30&limit=15 #傳回30 到 45的員工

如果用戶端沒有傳這些參數,則應使用預設值。通常預設值是offset = 0和limit = 10。如果資料庫檢索很慢,應當減小limit值。

/employees #傳回0 到 10的員工

此外,如果您使用分頁,用戶端需要知道資源總數。例:請求:

{
  "offset": 0,
  "limit": 10,
  "total": 3465,
  "employees": [
    //...
  ]
}      

非資源請求用動詞

有時API調用并不涉及資源(如計算,翻譯或轉換)。例:

GET /translate?from=de_DE&to=en_US&text=Hallo

GET /calculate?para2=23&para2=432

在這種情況下,API響應不會傳回任何資源。而是執行一個操作并将結果傳回給用戶端。是以,您應該在URL中使用動詞而不是名詞,來清楚的區分資源請求和非資源請求。

考慮特定資源搜尋和跨資源搜尋

提供對特定資源的搜尋很容易。隻需使用相應的資源集合URL,并将搜尋字元串附加到查詢參數中即可。

GET /employees?query=Paul

如果要對所有資源提供全局搜尋,則需要用其他方法。前文提到,對于非資源請求URL,使用動詞而不是名詞。是以,您的搜尋網址可能如下所示:

GET /search?query=Paul //傳回 employees, customers, suppliers 等等.

在響應參數中添加浏覽其它API的連結

理想情況下,不會讓用戶端自己構造使用REST API的URL。讓我們思考一個例子。

用戶端想要通路員工的薪酬表。為此,他必須知道他可以通過在員工URL(例如/employees/21/salaryStatements)中附加字元串“salaryStatements”來通路薪酬表。這個字元串連接配接很容易出錯,且難以維護。如果你更改了通路薪水表的REST API的方式(例如變成了/employees/21/salary-statement或/employees/21/paySlips),所有用戶端都将中斷。

更好的方案是在響應參數中添加一個links字段,讓用戶端可以自動變更。

請求:

GET /employees/

//...
   {
      "id":1,
      "name":"Paul",
      "links": [
         {
            "rel": "salary",
            "href": "/employees/1/salaryStatements"
         }
      ]
   },
//...      

如果用戶端完全依靠links中的字段獲得薪資表,你更改了API,用戶端将始終獲得一個有效的URL(隻要你更改了link字段,請求的URL會自動更改),不會中斷。另一個好處是,你的API變得可以自我描述,需要寫的文檔更少。

在分頁時,您還可以添加擷取下一頁或上一頁的連結示例。隻需提供适當的偏移和限制的連結示例。

GET /employees?offset=20&limit=10
{
  "offset": 20,
  "limit": 10,
  "total": 3465,
  "employees": [
    //...
  ],
  "links": [
     {
        "rel": "nextPage",
        "href": "/employees?offset=30&limit=10"
     },
     {
        "rel": "previousPage",
        "href": "/employees?offset=10&limit=10"
     }
  ]
}