1. 程式人生 > 其它 >REST API設計實踐

REST API設計實踐

目錄

1 RESTful

1.1 什麼是RESTful

官方解釋

Representational State Transfer 的簡稱,即 表現層狀態轉移。

人看的解釋

  • REST指一組架構約束條件和原則, 如果一個架構符合 REST 的約束條件和原則,就稱之為 RESTful 架構
  • RESTful 是一種軟體架構風格,而不是標準

大神總結

  • URL定位資源
  • HTTP動詞(GET, POST, DELETE, PUT)描述操作
  • HTTP狀態碼錶示響應結果

總的來說,RESTful是一種web服務設計風格,風格意思就是大家預設的但不是強制的。

1.2 REST規範

用URL定位資源

REST 的主體是資源,所謂“資源”,就是網路上的一個具體資訊,例如:一張圖片,一段文字、一種服務。總之就是一個實際存在的東西,而 URL 就是用來指向這個資源的。

例如:

https://api.example.com/users

用HTTP動詞描述操作

對資源的操作,無外乎 CRUD(增刪改查),RESTful 中,每個 HTTP 動詞對應一個 CRUD 操作。

  • GET:對應 Retrieve 操作。GET請求從不改變資源的狀態。無副作用。GET方法是冪等的。GET方法具有隻讀的含義。因此,你可以完美的使用快取。
  • POST:對應 Create 操作
  • DELETE:對應 Delete 操作
  • PUT:對應 Update 操作

用HTTP狀態碼錶示響應結果

RESTful Web服務應使用合適的HTTP狀態碼來響應客戶端請求

  • 2xx - 成功 - 一切都很好
  • 4xx - 客戶端錯誤 - 如果客戶端發生錯誤(例如客戶端傳送無效請求或未被授權)
  • 5xx – 伺服器錯誤 - 如果伺服器發生錯誤(例如,嘗試處理請求時出錯)

2 RESTful API設計實踐

設計RESTful API可能很棘手,因為沒有官方和強制標準。基本上,實現API的方法有很多種,但其中一些方法已在實踐中得到證明並被廣泛採用。這篇文章涵蓋了構建 HTTP 和 RESTful API 的最佳實踐。我們將討論 URL 結構、HTTP 方法、建立和更新資源、設計關係、有效負載格式、分頁、版本控制等等。

2.1 URL設計建議

用名詞代替動詞表示資源

這讓你的API更簡潔,URL數目更少

正確示例:
GET /employees
GET /employees?state=external
POST /employees
PUT /employees/56

錯誤示例:
/getAllEmployees
/getAllExternalEmployees
/createEmployee
/updateEmployee

資源用複數表示,全部小寫,用_或-連線,不要使用駝峰命名法

這是個人愛好問題,但複數形式更為常見。最重要的是:避免複數和單數名詞混合使用,這顯得非常混亂且容易出錯。

推薦:

/employees
/employees/21

不推薦:

/employee
/employee/21

每個資源使用兩個URL

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

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

在URL中強制加入版本號

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

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

正確示例:
/v1/employees

錯誤示例:
/v1.2/employees

提供分頁資訊

一次性返回資料庫所有資源不是一個好主意。因此,需要提供分頁機制。通常使用資料庫中眾所周知的引數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": [
    //...
  ]
}

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

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

正確示例:

GET /employees?state=internal&maturity=senior

錯誤示例:

GET /employees
GET /externalEmployees
GET /internalEmployees
GET /internalAndSeniorEmployees

非資源請求用動詞

有時API呼叫並不涉及資源(如計算,翻譯或轉換)。

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

//Trigger an operation that changes the server-side state
POST /restartServer
//no body

POST /banUserFromChannel
{ "user": "123", "channel": "serious-chat-channel" }

在這種情況下,不涉及任何資源,伺服器執行一個操作並將結果返回給客戶端。因此,您應該在 URL 中使用動詞而不是名詞來清楚地區分操作(RPC-style APIs)和用於領域建模的資源(REST APIs)。

  • 如果一個API已操作為主,應該設計成RPC風格
  • 如果一個API主要用來對相關資料進行CRUD,應該設計成REST風格

2.2 HTTP動詞設計建議

HTTP動詞語義

  • Get
    • 冪等
    • 只讀。GET永遠不會改變伺服器端資源的狀態,它必須沒有副作用。
  • PUT
    • 冪等
    • 可用於建立和更新
    • 常用於更新(完全更新)。例如:PUT /employees/1- 更新員工 1
    • 始終在請求中包含整個有效負載。要麼全有要麼全無。PUT不用於部分更新
  • POST
    • 非冪等
    • 用於建立。例如:POST /employees建立一個新員工
  • DELETE
    • 冪等
    • 用於刪除。例如:DELETE /employees/1
  • PATCH
    • 冪等
    • 用於部分更新。例如:PATCH /employees/1- 使用負載中包含的欄位更新員工1, 員工1的其他欄位不會被更新

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

POST(建立) DELETE(刪除) PUT(更新) GET(查詢)
/employees 建立一個新員工 刪除所有員工 批量更新員工資訊 列出所有員工
/employees/56 (錯誤) 刪除56號員工 更新56號員工的資訊 獲取56號員工的資訊

2.3 HTTP狀態碼設計建議

RESTful Web服務應使用合適的HTTP狀態碼來響應客戶端請求,其中的大部分HTTP狀態碼都不會被用到,只會用其中的一小部分。通常會用到一下幾個:

2xx:成功 3xx:重定向 4xx:客戶端錯誤 5xx:伺服器錯誤
200 成功 301 永久重定向 400 錯誤請求 500 內部伺服器錯誤
201 建立 304 資源未修改 401未授權
403 禁止
404 未找到

2.4 響應內容建議

使用小駝峰命名法

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

{ "yearOfBirth": 1982 }

不要使用下劃線(year_of_birth)或大駝峰命名法(YearOfBirth)。通常,RESTful Web服務將被JavaScript編寫的客戶端使用。客戶端會將JSON響應轉換為JavaScript物件(通過呼叫var person = JSON.parse(response)),然後呼叫其屬性。因此,最好遵循JavaScript程式碼通用規範

將實際資料包裝在一個data欄位中

GET響應,PUT、POST 和 PATCH 請求體都應該將實際資料包裝data欄位

  • 有剩餘空間可以新增元資料(例如用於分頁、連結、棄用警告、錯誤訊息)
  • 保持資料一致性
  • JSON:API 標準相容
GET /employees  # 返回data欄位中的物件列表

{
  "data": [
    { "id": 1, "name": "Larry" }
    , { "id": 2, "name": "Peter" }
  ]
}

GET /employees/1 # 返回data欄位中的單個物件

{
  "data": { 
    "id": 1, 
    "name": "Larry"
  }
}

返回有用的錯誤資訊

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

請求

GET /employees?state=super

響應

// 400 Bad Request
{
  "errors": [
    {
      "status": 400,
      "detail": "Invalid state. Valid values are 'internal' or 'external'",
      "code": 352,
      "links": {
        "about": "http://www.domain.com/rest/errorcode/352"
      }
    }
  ]
}

提供用於導航的連結

理想情況下,您不要讓您的客戶構建 URL 以使用您的 REST API。讓我們考慮一個例子。

客戶想要訪問員工的工資報表。因此,他必須知道他可以通過將查詢引數附加salaryStatements到員工 URL(例如/employees/21/salaryStatements)來訪問工資報表。這種字串連線容易出錯、脆弱且難以維護。如果您更改訪問 REST API 中的薪水報表的方式(例如,現在使用“salary-statements”或“paySlips”),所有客戶端都將中斷。

{
  "data": [
    {
      "id":1,
      "name":"Paul",
      "links": [
        {
          "salary": "http://www.domain.com/employees/1/salaryStatements"
        }
      ]
    }
  ]
}

如果客戶端完全依賴連結來獲取工資單,那麼即使您更改 API,他也不會中斷,因為客戶端將始終獲得有效的 URL(只要您在 URL 更改的情況下更新連結)。另一個好處是您的 API 變得更具自我描述性,並且客戶不必經常查詢文件。

適當的設計關係

讓我們假設每個employee都有 amanager和幾個teamMembers。在API中設計關係基本上有三個常用方式:連結、外來鍵和嵌入。

它們都是有效的,正確的選擇取決於用例。主要考慮

  • 客戶端的訪問模式
  • 可容忍的請求次數
  • 有效負載大小
連結:負載小,但要獲取完整資料訪問次數過多

{
  "data": [
    { 
      "id": 1, 
      "name": "Larry",
      "relationships": {
        "manager": "http://www.domain.com/employees/1/manager",
        "teamMembers": [ 
          "http://www.domain.com/employees/12",
          "http://www.domain.com/employees/13"
        ]
        //or "teamMembers": "http://www.domain.com/employees/1/teamMembers"
      }
    }
  ]
}

外來鍵:負載大小適中,沒有重複,但使用者需要解析資料,使用麻煩

{
  "data": [
    { 
      "id": 1, 
      "name": "Larry",
      "relationships": {
        "manager":  5 , 
        "teamMembers": [ 12, 13 ]
      }
    }
  ],
  "included": {
    "manager": {
      "id": 5, 
      "name": "Kevin"
    },
    "teamMembers": [
      { "id": 12, "name": "Albert" }
      , { "id": 13, "name": "Tom" }
    ]
  }
}

嵌入:使用者獲取資料最方便,但負載過大,引用資料會重複

{
  "data": [
    { 
      "id": 1, 
      "name": "Larry",
      "manager": {
        "id": 5, 
        "name": "Kevin"
      },
      "teamMembers": [
        { "id": 12, "name": "Albert" }
        , { "id": 13, "name": "Tom" }
      ]
    }
  ]
}

3 One More Things

3.1

參考資料