在項目中往往會需要確定一個好的API風格,到底有哪些風格可以參考,API Style 的細節要點有哪些呢?

Http API Style 有哪些?

  • SOAP:tend to be centered around operations that are usually use-case specific and specialized.
  • REST:centered around business (data) entities exposed as resources that are identified via URIs and can be manipulated via standardized CRUD-like methods using different representations, and hypermedia
  • GraphQL:a query language for APIs and a runtime for fulfilling those queries with your existing data

SOAP 風格(嚴格來說,算不上風格)最早於1998年由微軟提出;REST 風格於2000年 由 Roy Thomas Fielding 論文中提出;GraphQL 於2015年由 Facebook 提出;

  • SOAP vs REST

如果要輕鬆、快速地完成API設計,SOAP 風格的API就足夠了。畢竟REST有時很難做到,尤其是在一開始。但隨着時間的推移,使用RESET 風格的API,服務器端的演進變得更容易,客戶機對變化的適應能力也更強。

  • REST vs GraphQL

    REST 限於其歷史背景,對於 查詢 操作一些細節並沒有太多描述,隨着互聯網的發展,查詢的複雜度越來越高,而 GraphQL 是一個很好的補充。

在業界有將近70%的API是REST-like的風格,其中當然就包括谷歌、微軟等行業巨頭,REST 差不多已經成爲了事實上的標準,瞭解、用好 REST 十分必要。

REST 是什麼?

REST,全稱是 Resource Representational State Transfer:通俗來講就是:資源在網絡中以某種表現形式進行狀態轉移。分解開來:

  • Resource:資源,即數據(前面說過網絡的核心)。比如 newsfeed,friends等;
  • Representational:某種表現形式,比如用JSON,XML,JPEG等;
  • State Transfer:狀態變化。通過HTTP動詞實現。

資源模型

資源是具有類型、數據、與其他資源關係、以及一組對其進行操作的方法的對象。

Richardson成熟度模型

Level 0

不使用任何URI,HTTP方法和HATEOAS功能。

該模型的出發點是使用HTTP作爲遠程交互的傳輸系統,但不使用Web的任何機制。基本上就是使用HTTP作爲你遠程交互機中的隧道機制,通常基於“遠程過程調用“(RPC, Remote Procedure Invocation )。

Level 1 - Resources

使用URI、HTTP方法、HATEOAS中的 URI

邁向REST的第一步就是引入資源的概念。接下來,我們所要討論的是各個資源,而不是將所有請求發送到單一的服務端點。每個資源都由唯一的URI單獨標識

Level 2 - HTTP Verbs

使用URI、HTTP方法、HATEOAS中 的URI和HTTP

支持每個公開資源上的幾個HTTP謂詞 - 創建,讀取,更新和刪除(CRUD)服務。通常代表業務實體的資源狀態可以通過網絡進行操作。

Level 3 - Hypermedia Controls

使用所有三個,即URI,HTTP和HATEOAS。

超媒體控制的關鍵在於它告訴我們下一步我們可以做什麼,以及操作所需資源的URI。與我們必須提前知道在哪裏創建預約請求不同(Level2中),在響應中的 HATEOAS 告訴了我們下一步該如何做,以完成應用程序狀態轉換。

關鍵問題

REST 本身不是標準,只是一種風格。因此只要遵從該風格,都是OK的。然而,除此之外我們逃不開使用中遇到的很多問題,最典型的問題,如下:

  • Error Handling
  • Sorting
  • Pagination
  • versioning
  • filtering
  • Long running
  • Sub-collection
  • Action(i.e. Batch Operation)

Error handling

詳見:跨服務錯誤處理

Sorting

如果 API 方法允許客戶端指定列表結果的排序順序,則請求消息 應該 包含一個字段:

string order_by = ...;

說明:語法中的冗餘空格字符是無關緊要的。 "foo,bar desc"" foo , bar desc " 是等效的。

字符串值 應該 遵循 SQL 語法:逗號分隔的字段列表。例如: "foo,bar" 。默認排序順序爲升序。要將字段指定爲降序, 應該 將後綴 " desc" 附加到字段名稱中。例如: "foo desc,bar"

Pagination

  • 可列表集合 應該 支持分頁,即使結果通常很小。

說明:如果某個 API 從一開始就不支持分頁,稍後再支持它就比較麻煩,因爲添加分頁會破壞 API 的行爲。 不知道 API 正在使用分頁的客戶端可能會錯誤地認爲他們收到了完整的結果,而實際上只收到了第一頁。

  • 翻頁方式

    後臺存儲 Request Response
    搜索引擎 {
    “page_num”: 1, // 頁碼從 1 開始
    “page_size”: 10
    }
    {
    “code”: 0,
    “msg”: “”,
    “data”: {
    “total_cnt”: 100,
    “items”: []
    }
    }
    數據庫 {
    “last_id”: 1, // 第一頁,默認傳0
    “page_size”: 10
    }
    {
    “code”: 0,
    “msg”: “”,
    “data”: {
    “items”: [],
    “next_id”: 10 // 下一頁放到last_id的值
    }
    }

Versioning

業界方案

  • 無版本控制

    這是最簡單的方法,它對於一些內部 API 來說可能是可以接受的。 重大變化可以表示爲新資源或新鏈接。 向現有資源添加內容可能不會帶來重大更改,因爲不希望看到此內容的客戶端應用程序將忽略它。

    https://adventure-works.com/customers/3
  • URI 版本控制

    每次修改 Web API 或更改資源的架構時,向每個資源的 URI 添加版本號。 以前存在的 URI 應像以前一樣繼續運行,並返回符合原始架構的資源。

    https://adventure-works.com/v2/customers/3
  • 查詢字符串版本控制

    不是提供多個 URI,而是可以通過在追加到 HTTP 請求後面的查詢字符串中使用參數來指定資源的版本,例如

    https://adventure-works.com/customers/3?version=2

    注意:某些較舊的 Web 瀏覽器和 Web 代理不會緩存在 URI 中包含查詢字符串的請求的響應。 這可能會降低使用 Web API 並在此類 Web 瀏覽器中運行的 Web 應用程序的性能。

  • 標頭版本控制

    不是追加版本號作爲查詢字符串參數,而是可以實現指示資源的版本的自定義標頭。 此方法需要客戶端應用程序將相應標頭添加到所有請求,雖然如果省略了版本標頭,處理客戶端請求的代碼可以使用默認值(版本 1)。 下面的示例使用了名爲 Custom-Header 的自定義標頭**。 此標頭的值指示 Web API 的版本。

    GET https://adventure-works.com/customers/3 HTTP/1.1
    Custom-Header: api-version=1
  • 無版本控制:在更改RESTful API時,請以兼容的方式進行更改,並避免生成其他API版本。

    說明:多個版本會使理解、測試、維護、發展、操作和發佈我們的系統變得非常複雜。

在更改RESTful api時,請以兼容的方式進行更改,並避免生成其他API版本。多個版本可能會顯著地複雜化查看、測試、維護、發展、運營和發佈系統( 補充閱讀 )。如果無法以兼容的方式更改API,請使用這三種方式:

  • 在舊資源變體的基礎上創建新資源(變量)
  • 創建一個新的服務端點-即一個具有新API的新應用程序(使用新域名)
  • 在微服務中創建一個新的API版本,該版本支持與舊API同時支持

Filtering

在參數過濾時通常會爲參數值定義數據格式。爲了在所有 API 中提供一致的開發者體驗並減少學習曲線,API 設計人員 必須 使用以下 擴展巴科斯範式 (Extended Backus-Naur Form,簡寫爲“EBNF”)語法的變體來定義這樣的語法:

Production  = name "=" [ Expression ] ";" ;
Expression  = Alternative { "|" Alternative } ;
Alternative = Term { Term } ;
Term        = name | TOKEN | Group | Option | Repetition ;
Group       = "(" Expression ")" ;
Option      = "[" Expression "]" ;
Repetition  = "{" Expression "}" ;

注意: TOKEN 表示在語法之外定義的終端符號。

Example

GET /zoos?id=1001,1002,1003

Long running

有時,POST、PUT、PATCH 或 DELETE 操作可能需要一段時間才能完成。如果需要等待該操作完成後才能向客戶端發送響應,可能會造成不可接受的延遲。在這種情況下,請考慮將該操作設置爲異步操作。返回 HTTP 狀態代碼 202(已接受),指示該請求已接受進行處理,但尚未完成。

應公開一個可返回異步請求狀態的終結點,使客戶端能夠通過輪詢狀態終結點來監視狀態。在 202 響應的 Location 標頭中包含狀態終結點的 URI。例如:

HTTP/1.1 202 Accepted
Location: /api/status/12345

如果客戶端向此終結點發送 GET 請求,響應中應包含該請求的當前狀態。(可選)響應中還可以包含預計完成時間,或者用於取消操作的鏈接

HTTP/1.1 200 OK
Content-Type: application/json

{
    "status":"In progress",
    "link": { "rel":"cancel", "method":"delete", "href":"/api/status/12345" }
}

如果異步操作創建了新資源,則該操作完成後,狀態終結點應返回狀態代碼 303(查看其他)。在 303 響應中,包含一個 Location 標頭用於提供新資源的 URI:

HTTP/1.1 303 See Other
Location: /api/orders/12345

有關詳細信息,請參閱 異步請求-回覆模式

Sub-collection

有時,API 需要讓客戶跨子集執行 List/Search 操作。例如,“API 圖書館”有一組書架,每個書架都有一系列書籍,而客戶希望在所有書架上搜索某一本書。在這種情況下,建議在子集合上使用標準 List ,併爲父集合指定通配符集合 ID "-" 。對於“API 圖書館”示例,我們可以使用以下 REST API 請求:

GET https://library.googleapis.com/v1/shelves/-/books/{id}

注意:選擇 "-" 而不是 "*" 的原因是爲了避免需要進行 URL 轉義。

Action

常用的HTTP動詞有下面五個(括號裏是對應的SQL命令)。

  • GET(SELECT):從服務器取出資源(一項或多項)。
  • POST(CREATE):在服務器新建一個資源。
  • PUT(UPDATE):在服務器更新資源(客戶端提供改變的屬性)
  • DELETE(DELETE):從服務器刪除資源。

對於非標準的操作,以上動詞無法無法滿足需求,可以在資源上使用“操作”子集合。 動作基本上類似於RPC的消息,用於對資源執行特定操作。 “動作”子集合可以看作是一個命令隊列,可以將新的動作發佈到該命令隊列中,然後由API執行。定義標準動詞如下:

  1. batch:批量操作
  2. search:搜索操作
GET /zoos:列出所有動物園
POST /zoos:新建一個動物園
GET /zoos/ID:獲取某個指定動物園的信息
PUT /zoos/ID:更新某個指定動物園的信息(提供該動物園的全部信息)
PATCH /zoos/ID:更新某個指定動物園的信息(提供該動物園的部分信息)
DELETE /zoos/ID:刪除某個動物園
GET /zoos/ID/animals:列出某個指定動物園的所有動物
DELETE /zoos/ID/animals/ID:刪除某個指定動物園的指定動物
GET /zoos/-/action/batch  批量查詢
POST /zoos/-/action/batch 批量更新
POST /zoos/-/action/search 搜索

其他

  • null 和不存在的屬性使用相同的語義

    required nullable {} {“example”:null}
    true true ✗ No ✔ Yes
    false true ✔ Yes ✔ Yes
    true false ✗ No ✗ No
    false false ✔ Yes ✗ No
  • 路徑使用 中劃線 - 代替 下劃線 _

    在搜索引擎中,把中劃線當做空格處理,而下劃線是被忽略的。使用中劃線是對搜索引擎友好的寫法

    Example:

    /shipment-orders/{shipment-order-id}
  • 範圍

    表示範圍的字段 應該 使用半開區間和命名慣例 [start_xxx, end_xxx) ,例如 [start_key, end_key)[start_time, end_time) 。通常 C ++ STL 庫和 Java 標準庫會使用半開區間語義。API 應該 避免使用其他表示範圍的方式,例如 (index, count)[first, last]

總結

完成以上這些,也僅僅是達到REST Level 2,由於Level 3 對於API風格影響不大,暫不涉及。對 HATEOAS 感興趣,可以參考 Github v3 版本的API。

參考鏈接

本文作者:cyningsun

本文地址: https://www.cyningsun.com/06-29-2020/how-to-write-restful-api.html

版權聲明:本博客所有文章除特別聲明外,均採用 CC BY-NC-ND 3.0 CN 許可協議。轉載請註明出處!

相關文章