這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
原文:RESTful API Design. Best Practices in a Nutshell.
作者:Philipp Hauer
項目資源的URL應該如何設計?用名詞複數還是用名詞單數?一個資源需要多少個URL?用哪種HTTP方法來建立一個新的資源?選擇性參數應該放在哪裡?那些不涉及資源操作的URL呢?實現分頁和版本控制的最好方法是什嗎?因為有太多的疑問,設計RESTful API變得很棘手。在這篇文章中,我們來看一下RESTful API設計,並給出一個最佳實務方案。
每個資源使用兩個URL
資源集合用一個URL,具體某個資源用一個URL:
/employees #資源集合的URL/employees/56 #具體某個資源的URL
用名詞代替動詞表示資源
這讓你的API更簡潔,URL數目更少。不要這麼設計:
/getAllEmployees/getAllExternalEmployees/createEmployee/updateEmployee
更好的設計:
GET /employeesGET /employees?state=externalPOST /employeesPUT /employees/56
用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來建立新的資源過程
- 用戶端向資源集合URL
/employees發送POST請求。HTTP body 包含新資源的屬性 “Albert Stark”。
- RESTful Web伺服器為新員工產生ID,在其內部模型中建立員工,並向用戶端發送響應。這個響應的HTTP頭部包含一個Location欄位,指示建立資源可訪問的URL。
對具體資源的URL使用PUT方法,來更新資源
使用PUT更新已有資源。
- 用戶端向具體資源的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 /employeesGET /externalEmployeesGET /internalEmployeesGET /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的員工
此外,如果您使用分頁,用戶端需要知道資源總數。例:請求:
GET /employees
響應:
{ "offset": 0, "limit": 10, "total": 3465, "employees": [ //... ]}
非資源請求用動詞
有時API調用並不涉及資源(如計算,翻譯或轉換)。例:
GET /translate?from=de_DE&to=en_US&text=HalloGET /calculate?para2=23¶2=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" } ]}
相關閱讀:
- 作者寫的一篇關於在Java中測試RESTful服務的最佳實務的文章。
- 作者強烈推薦一本書Brain Mulloy’s nice paper,作為這篇文章的基礎。