RESTful Web API 實作是一種 Web API,採用表示式狀態傳輸 (REST) 架構原則,在用戶端與服務之間達成無狀態、鬆散結合的介面。 RESTful 的 Web API 支援標準 HTTP 通訊協定,以對資源執行作業,並傳回包含超媒體連結和 HTTP 作業狀態代碼的資源表示法。
RESTful Web API 應符合下列原則:
平台獨立性,這表示用戶端可以呼叫Web API,而不論內部實作為何。 為了達到平台獨立性,Web API 會使用 HTTP 作為標準通訊協定、提供清楚的檔,並支援熟悉的數據交換格式,例如 JSON 或 XML。
鬆散結合,這表示用戶端和Web服務可以獨立發展。 用戶端不需要知道 Web 服務的內部實作,而 Web 服務不需要知道客戶端的內部實作。 若要在 RESTful Web API 中達成鬆散結合,請只使用標準通訊協議並實作一種機制,讓用戶端和 Web 服務同意要交換的數據格式。
本文說明設計 RESTful Web API 的最佳做法。 它也涵蓋建置 Web API 的常見設計模式和考慮,這些 API 易於瞭解、彈性且易於維護。
RESTful Web API 設計概念
若要實作 RESTful Web API,您必須瞭解下列概念。
統一資源標識碼 (URI): REST API 是針對資源所設計,其為用戶端可存取的任何對象、數據或服務。 每個資源都會以可唯一識別該資源的 URI 來表示。 例如,特定客戶訂單的 URI 可能是:
https://api.contoso.com/orders/1
資源表示 定義由其 URI 識別的資源如何經由 HTTP 協定,以特定格式(例如 XML 或 JSON)進行編碼和傳輸。 想要擷取特定資源的客戶端必須使用 API 要求中的資源 URI。 API 會傳回 URI 所指出之數據的資源表示法。 例如,用戶端可以向 URI 識別碼
https://api.contoso.com/orders/1
提出 GET 要求,以接收下列 JSON 主體:{"orderId":1,"orderValue":99.9,"productId":1,"quantity":1}
統一介面 是 RESTful API 如何實現客戶端與服務實作之間的鬆散結合。 針對建置在 HTTP 上的 REST API,統一介面包含使用標準 HTTP 動詞來執行
GET
、POST
、PUT
、PATCH
和DELETE
等資源作業。無狀態要求模型: RESTful API 會使用無狀態要求模型,這表示 HTTP 要求是獨立的,而且可能會依任何順序發生。 因此,在要求之間保留暫時性狀態資訊不可行。 儲存資訊的唯一位置是資源本身,而且每個要求都應該是原子操作。 無狀態要求模型支援高延展性,因為它不需要保留用戶端與特定伺服器之間的任何親和性。 不過,無狀態模型也可以限制延展性,因為 Web 服務後端記憶體延展性面臨挑戰。 如需相應放大數據存放區策略的詳細資訊,請參閱 數據分割。
超媒體連結: REST API 可由包含在每個資源表示法中的超媒體連結所驅動。 例如,下列程式代碼區塊會顯示訂單的 JSON 表示法。 其中包含可取得或更新與訂單相關聯之客戶的連結。
{ "orderID":3, "productID":2, "quantity":4, "orderValue":16.60, "links": [ {"rel":"product","href":"https://api.contoso.com/customers/3", "action":"GET" }, {"rel":"product","href":"https://api.contoso.com/customers/3", "action":"PUT" } ] }
定義 RESTful Web API 資源 URI
RESTful Web API 會圍繞資源進行組織。 若要圍繞資源組織 API 設計,請定義對應至商務實體的資源 URI。 可能的話,將資源 URI 基於名詞(資源),而不是動詞(操作)。
例如,在電子商務系統中,主要商務實體可能是客戶和訂單。 若要建立訂單,用戶端會將 HTTP POST 要求中的訂單資訊傳送至資源 URI。 要求的 HTTP 回應會指出訂單建立是否成功。
建立訂單資源的 URI 可能類似:
https://api.contoso.com/orders // Good
避免在 URI 中使用動詞來表示作業。 例如,不建議使用下列 URI:
https://api.contoso.com/create-order // Avoid
實體通常會被分組成為例如客戶或訂單的集合。 集合是與集合內專案不同的資源,因此應該有自己的 URI。 例如,下列 URI 可能代表訂單集合:
https://api.contoso.com/orders
用戶端擷取集合之後,即可對每個專案的URI提出GET要求。 例如,若要接收特定訂單的相關信息,用戶端會在 URI https://api.contoso.com/orders/1
上執行 HTTP GET 要求,以接收下列 JSON 主體做為內部訂單數據的資源表示法:
{"orderId":1,"orderValue":99.9,"productId":1,"quantity":1}
資源 URI 命名慣例
當您設計 RESTful Web API 時,請務必針對資源使用正確的命名和關聯性慣例:
針對資源名稱使用名詞。 使用名詞來表示資源。 例如,使用
/orders
而不是/create-order
。 HTTP GET、POST、PUT、PATCH 和 DELETE 方法已經表示口頭動作。使用複數名詞來命名集合 URI。 一般而言,它有助於針對參考集合的 URI 使用複數名詞。 最好將集合和專案的 URI 組織成階層。 例如,
/customers
是客戶集合的路徑,而/customers/5
是識別碼為5的客戶之路徑。 這種方法有助於讓 Web API 保持直覺。 此外,許多 Web API 架構可以根據參數化 URI 路徑來路由要求,因此您可以定義路徑/customers/{id}
的路由。請考慮不同資源類型之間的關聯性,以及如何公開這些關聯。 例如,
/customers/5/orders
可能會代表客戶 5 的所有訂單。 您也可以藉由從訂單到客戶的角度來呈現關聯性,以不同方向來處理關係。 在這裡案例中,URI 可能是/orders/99/customer
。 不過,擴充此模型太遠可能會變得繁瑣而無法實作。 更好的方法是在 HTTP 回應消息的本文中包含連結,讓用戶端可以輕鬆地存取相關資源。 使用超文本做為應用程式狀態引擎 (HATEOAS) 以啟用相關資源的導覽 ,更詳細地描述此機制。保持關聯性簡單且有彈性。 在更複雜的系統中,您可能會傾向於提供 URI,讓用戶端瀏覽數個層級的關聯性,例如
/customers/1/orders/99/products
。 不過,如果資源之間的關聯性未來變更,這種複雜度層級可能會難以維護,而且無法靈活。 相反地,請嘗試讓 URI 保持相對簡單。 在應用程式具有資源的參考之後,您應該能夠使用此參考來尋找與該資源相關的專案。 您可以將上述查詢取代為 URI/customers/1/orders
來尋找客戶 1 的所有訂單,然後使用/orders/99/products
來尋找此訂單中的產品。小提示
請避免要求比 集合/專案/集合更複雜的資源 URI。
避免大量小型資源。 所有 Web 要求都會對網頁伺服器施加負載。 要求越多,負載就越大。 公開大量小型資源的 Web API 稱為 聊天式 Web API。 嘗試避免這些 API,因為它們需要用戶端應用程式傳送多個要求,以尋找它所需的所有數據。 相反地,請考慮將數據反正規化,並將相關信息合併成可透過單一要求擷取的較大資源。 不過,您仍然需要平衡此方法與擷取用戶端不需要之數據的額外負荷。 大型物件擷取可能會增加要求的延遲,並產生更多頻寬成本。 如需這些效能反模式的詳細資訊,請參閱 Chatty I/O 和 外部擷取。
避免建立能鏡像資料庫內部結構的 API。 REST 的目的是建立商務實體的模型,以及應用程式可以在這些實體上執行的作業。 客戶不應該接觸到內部實現。 例如,如果您的數據儲存在關係資料庫中,Web API 就不需要將每個數據表公開為資源的集合。 這種方法會增加受攻擊面,並可能導致數據外洩。 相反地,請將 Web API 視為資料庫的抽象概念。 如有必要,請在資料庫與 Web API 之間引入對應層。 此層可確保用戶端應用程式與基礎資料庫架構的變更隔離。
小提示
可能無法將 Web API 所實作的每個作業對應至特定資源。 您可以透過叫用函式的 HTTP 要求來處理這些 非資源 案例,並以 HTTP 回應訊息的形式傳回結果。
例如,實作新增和減去等簡單計算機作業的 Web API 可以提供 URI,以虛擬資源的形式公開這些作業,並使用查詢字串來指定必要的參數。 URI /add?operand1=99&operand2=1 的 GET 請求會傳回內含值 100 的回應訊息。
不過,您應該謹慎使用這些形式的 URI。
定義 RESTful Web API 方法
RESTful Web API 方法會與 HTTP 通訊協定所定義的要求方法和媒體類型一致。 本節說明最常見的要求方法和 RESTful Web API 中使用的媒體類型。
HTTP 要求方法
HTTP 通訊協定會定義許多要求方法,指出您想要在資源上執行的動作。 RESTful Web API 中使用的最常見方法是 GET、 POST、 PUT、 PATCH 和 DELETE。 每個方法都會對應至特定作業。 當您設計 RESTful Web API 時,請以與通訊協定定義一致的方式使用這些方法、要存取的資源,以及正在執行的動作。
請務必記住,特定要求方法的效果應取決於資源是集合或清單還是單個項目。 下表包含大部分 RESTful 實作所使用的一些慣例。
這很重要
下表使用範例電子商務 customer
實體。 Web API 不需要實作所有要求方法。 其執行的方法取決於特定情境。
資源 | 貼文 | 獲取 | 放 | 刪除 |
---|---|---|---|---|
/客戶 | 建立新的客戶 | 提取所有客戶 | 批次更新客戶 | 拿掉所有客戶 |
/customers/1 | 錯誤 | 擷取客戶 1 的詳細數據 | 如果存在,請更新客戶 1 的詳細數據 | 移除客戶1 |
/customers/1/orders | 為客戶1建立新訂單 | 擷取客戶 1 的所有訂單 | 客戶 1 的訂單大量更新 | 拿掉客戶 1 的所有訂單 |
GET 要求
GET 要求會擷取指定 URI 的資源表示法。 回應訊息的本文包含所要求資源的詳細數據。
GET 要求應該傳回下列其中一個 HTTP 狀態代碼:
HTTP 狀態代碼 | 原因 |
---|---|
200 (確定) | 方法已成功傳回資源。 |
204 (沒有內容) | 回應主體不包含任何內容,例如當搜尋要求時,HTTP 回應中沒有符合的結果。 |
404 (找不到) | 找不到要求的資源。 |
POST 要求
POST 要求應該建立一個資源。 伺服器會指派新資源的 URI,並將該 URI 傳回給用戶端。
這很重要
針對 POST 要求,用戶端不應該嘗試建立自己的 URI。 客戶端應該將要求提交至集合的 URI,伺服器應該將 URI 指派給新的資源。 如果客戶端嘗試建立自己的 URI,併發出特定 URI 的 POST 要求,伺服器會傳回 HTTP 狀態代碼 400 (BAD REQUEST),表示不支援此方法。
在 RESTful 模型中,POST 要求可用來將新的資源新增至 URI 所識別的集合。 不過,POST 要求也可以用來提交數據處理至現有資源,而不需要建立任何新資源。
POST 要求應該傳回下列其中一個 HTTP 狀態代碼:
HTTP 狀態代碼 | 原因 |
---|---|
200 (確定) | 方法已完成一些處理,但不會建立新的資源。 作業的結果可能會包含在回應本文中。 |
201 (已建立) | 已成功建立資源。 新資源的 URI 會包含在回應的 Location 標頭中。 回應內容包含資源的呈現。 |
204 (沒有內容) | 回應本文不包含任何內容。 |
400 (錯誤的要求) | 用戶端在要求中放置了無效的數據。 回應主體可以包含關於錯誤的更多資訊,或連結至提供更多詳細資訊的 URI。 |
405 (不允許的方法) | 用戶端已嘗試對不支援 POST 要求的 URI 提出 POST 要求。 |
PUT 要求
PUT 要求應在現有資源存在時更新該資源,或者在某些情況下,如果資源不存在,則建立新的資源。 若要提出PUT要求:
- 用戶端會指定資源的 URI,並包含包含資源完整表示法的要求主體。
- 用戶端提出要求。
- 如果已有此 URI 的資源已存在,則會加以取代。 否則,如果路由支援,就會建立新的資源。
PUT 方法會套用至個別項目的資源,例如特定客戶,而不是集合。 伺服器可能支援更新,但不支援透過PUT建立。 是否支援透過PUT進行建立,取決於用戶端是否能在資源存在之前,有意義且可靠地指派URI給資源。 如果無法,請使用POST來建立資源,並讓伺服器指派URI。 然後使用 PUT 或 PATCH 來更新 URI。
這很重要
PUT 要求必須是冪等,這表示多次提交相同的要求將一律導致使用相同的值修改相同的資源。 如果用戶端重新傳送 PUT 要求,結果應該維持不變。 相反地,POST 和 PATCH 請求不保證具有等冪特性。
PUT 要求應該傳回下列其中一個 HTTP 狀態代碼:
HTTP 狀態代碼 | 原因 |
---|---|
200 (確定) | 已成功更新資源。 |
201 (已建立) | 已成功建立資源。 響應主體可能包含資源的表示。 |
204 (沒有內容) | 已成功更新資源,但回應本文不包含任何內容。 |
409 (衝突) | 因為與資源的目前狀態發生衝突,因此無法完成要求。 |
小提示
請考慮實作大量 HTTP PUT 作業,以批處理集合中的多個資源更新。 PUT 要求應該指定集合的 URI。 要求本文應指定要修改之資源的詳細數據。 這種方法有助於減少閒聊並改善效能。
PATCH 要求
PATCH 要求會對現有資源執行部分更新。 用戶端會指定資源的 URI。 要求正文指定一系列要套用至資源的變更。 這個方法比使用 PUT 要求更有效率,因為用戶端只會傳送變更,而不是整個資源表示法。 如果伺服器支援此動作,PATCH 也可以指定一組空白或 Null 資源的更新,以建立新的資源。
透過 PATCH 要求,用戶端會以修補程式檔的形式,將一組更新傳送至現有的資源。 伺服器會處理修補程式檔以執行更新。 修補程式檔只會指定要套用的一組變更,而不是描述整個資源。 PATCH 方法 的規格 RFC 5789 不會定義修補程式檔的特定格式。 格式必須從要求中的媒體類型推斷。
JSON 是 Web API 最常見的數據格式之一。 兩個主要 JSON 型修補程式格式為 JSON 修補程式和 JSON 合併修補程式。
JSON 合併修補程式比 JSON 補丁簡單。 修補程式文件的結構與原始 JSON 資源相同,但只包含應變更或新增的字段子集。 此外,您可以透過在修補文件中指定null
作為字段值來刪除字段。 此規格表示,如果原始資源可以有明確的 Null 值,合併修補程式就不適合。
例如,假設原始資源具有下列 JSON 表示法:
{
"name":"gizmo",
"category":"widgets",
"color":"blue",
"price":10
}
以下是此資源的可能 JSON 合併補丁:
{
"price":12,
"color":null,
"size":"small"
}
此合併修補程式會告知伺服器更新 price
、刪除 color
和新增 size
。
name
和 category
的值不會修改。 如需 JSON 合併修補程式的詳細資訊,請參閱 RFC 7396。 JSON 合併修補程式的媒體類型為 application/merge-patch+json
。
如果原始資源可以包含具有特殊意義的 Null 值,那麼由於修補檔中的 null
,合併修補並不合適。 修補程式檔也不會指定伺服器應套用更新的順序。 此順序是否重要取決於數據和網域。
RFC 6902 中定義的 JSON 修補程式更有彈性,因為它會將變更指定為要套用的作業序列,包括新增、移除、取代、複製和測試,以驗證值。 JSON 修補程式的媒體類型為 application/json-patch+json
。
PATCH 要求應該傳回下列其中一個 HTTP 狀態代碼:
HTTP 狀態代碼 | 原因 |
---|---|
200 (確定) | 已成功更新資源。 |
400 (錯誤的要求) | 格式不正確的修補檔。 |
409 (衝突) | 修補程式檔有效,但變更無法套用至目前狀態的資源。 |
415 (不支援的媒體類型) | 不支援補丁文件格式。 |
DELETE 請求
DELETE 要求會移除位於指定 URI 的資源。 DELETE 要求應該傳回下列其中一個 HTTP 狀態代碼:
HTTP 狀態代碼 | 原因 |
---|---|
204 (無內容) | 已成功刪除資源。 已成功處理此程序,且回應內容未包含任何進一步資訊。 |
404 (找不到) | 資源不存在。 |
資源MIME類型
資源表示法是 URI 所識別的資源如何以特定格式透過 HTTP 通訊協定編碼和傳輸,例如 XML 或 JSON。 想要擷取特定資源的客戶端必須使用 API 要求中的 URI。 API 會傳回 URI 所指示之數據的資源表示法來回應。
在 HTTP 通訊協定中,資源表示格式是使用媒體類型來指定,也稱為 MIME 類型。 對於非二元數據,大部分的 Web API 都支援 JSON(媒體類型 = application/json
) 和可能 XML (媒體類型 = application/xml
)。
要求或回應中的 Content-Type 標頭會指定資源表示格式。 下列範例示範包含 JSON 數據的 POST 要求:
POST https://api.contoso.com/orders
Content-Type: application/json; charset=utf-8
Content-Length: 57
{"Id":1,"Name":"Gizmo","Category":"Widgets","Price":1.99}
如果伺服器不支援媒體類型,它應該會傳回 HTTP 狀態代碼 415(不支援的媒體類型)。
用戶端要求可以包含 Accept 標頭,其中包含用戶端在回應訊息中從伺服器接受的媒體類型清單。 例如:
GET https://api.contoso.com/orders/2
Accept: application/json, application/xml
如果伺服器不符合任何列出的媒體類型,它應該會傳回 HTTP 狀態代碼 406 (不可接受)。
實作異步方法
有時候,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 回應中,包含位置標頭,以提供新資源的 URI:
HTTP/1.1 303 See Other
Location: /api/orders/12345
如需詳細資訊,請參閱為長時間執行的要求提供非同步支援和非同步 Request-Reply 模式。
實作數據分頁和篩選
若要優化數據擷取並減少承載大小,請在 API 設計中實作數據分頁和查詢型篩選。 這些技術可讓用戶端只要求所需的數據子集,進而改善效能並減少頻寬使用量。
分頁 將大型數據集劃分成較小的易於管理的區塊。 使用類似 的
limit
查詢參數來指定要傳回的項目數,以及offset
指定起點。 請務必也為limit
和offset
提供有意義的預設值,例如limit=25
和offset=0
。 例如:GET /orders?limit=25&offset=50
limit
:指定要傳回的項目數目上限。小提示
為了協助防止拒絕服務攻擊,請考慮對傳回的項目數施加上限。 例如,如果您的服務設定
max-limit=25
,而用戶端要求limit=1000
,您的服務可能會根據 API 文件返回 25 項資料或 HTTP BAD-REQUEST 錯誤。offset
:指定數據的起始索引。
篩選 可讓用戶端套用條件來精簡數據集。 API 可讓用戶端在 URI 的查詢字串中傳遞篩選:
GET /orders?minCost=100&status=shipped
-
minCost
:篩選成本最低為 100 的訂單。 -
status
:篩選具有特定狀態的訂單。
-
考量下列最佳做法:
排序 可讓用戶端使用
sort
之類的sort=price
參數來排序數據。這很重要
排序方法可能會對快取產生負面影響,因為查詢字串參數構成許多快取實作作為快取數據索引鍵的資源標識符一部分。
用戶端定義投影的欄位選取 可讓用戶端只使用
fields
之類的fields=id,name
參數來指定所需的欄位。 例如,您可以使用可接受以逗號分隔的欄位清單的查詢字串參數,例如 /orders?fields=ProductID,Quantity。
您的 API 必須驗證要求的欄位,以確保允許用戶端存取這些欄位,而且不會公開透過 API 通常無法取得的欄位。
支援部分回應
某些資源包含大型二進位欄位,例如檔案或影像。 若要克服不可靠和間歇性連線所造成的問題,並改善回應時間,請考慮支援部分擷取大型二進位資源。
為了支援部分回應,Web API 應該支援大型資源的 GET 要求 Accept-Ranges 標頭。 此標頭表示 GET 作業支援部分要求。 用戶端應用程式可以提交 GET 要求,以傳回資源子集,指定為位元組範圍。
此外,請考慮實作這些資源的 HTTP HEAD 要求。 HEAD 要求與 GET 要求類似,不同之處在於它只會傳回描述資源的 HTTP 標頭,其中包含空的訊息本文。 用戶端應用程式可以發出 HEAD 要求,以判斷是否使用部分 GET 要求來擷取資源。 例如:
HEAD https://api.contoso.com/products/10?fields=productImage
以下是回應訊息範例:
HTTP/1.1 200 OK
Accept-Ranges: bytes
Content-Type: image/jpeg
Content-Length: 4580
Content-Length 標頭會提供資源的總大小,而 Accept-Ranges 標頭表示對應的 GET 作業支援部分結果。 用戶端應用程式可以使用這項資訊,以較小的區塊擷取映像。 第一個要求會使用 Range 標頭擷取前 2,500 個字節:
GET https://api.contoso.com/products/10?fields=productImage
Range: bytes=0-2499
回應消息透過返回 HTTP 狀態代碼 206,表明此回應是不完整的。 Content-Length 標頭會指定訊息本文中傳回的實際位元組數目,而不是資源的大小。 Content-Range 標頭會指出傳回資源中的哪個部分(位元組 0-2499 在 4580 中):
HTTP/1.1 206 Partial Content
Accept-Ranges: bytes
Content-Type: image/jpeg
Content-Length: 2500
Content-Range: bytes 0-2499/4580
[...]
用戶端應用程式的後續要求可以擷取資源的其餘部分。
實作HATEOAS
使用 REST 的其中一個主要原因是能夠巡覽整個資源集,而不需要事先知道 URI 架構。 每個 HTTP GET 要求都應該傳回必要的資訊,以透過回應中包含的超連結,直接尋找與所要求對象相關的資源。 應該向請求提供資訊,以說明在這些資源上可用的操作。 此原則稱為 HATEOAS,或超文本作為應用程式狀態的引擎。 系統實際上是有限的狀態機器,而且每個要求的回應都包含從某個狀態移至另一個狀態所需的資訊。 不需要其他資訊。
備註
沒有一般用途的標準會定義如何建立HATEOAS原則的模型。 本節中的範例說明一個可能專屬的解決方案。
例如,若要處理訂單與客戶之間的關聯性,訂單的表示法可能包含可識別訂單客戶可用作業的連結。 下列程式代碼區塊是可能的表示法:
{
"orderID":3,
"productID":2,
"quantity":4,
"orderValue":16.60,
"links":[
{
"rel":"customer",
"href":"https://api.contoso.com/customers/3",
"action":"GET",
"types":["text/xml","application/json"]
},
{
"rel":"customer",
"href":"https://api.contoso.com/customers/3",
"action":"PUT",
"types":["application/x-www-form-urlencoded"]
},
{
"rel":"customer",
"href":"https://api.contoso.com/customers/3",
"action":"DELETE",
"types":[]
},
{
"rel":"self",
"href":"https://api.contoso.com/orders/3",
"action":"GET",
"types":["text/xml","application/json"]
},
{
"rel":"self",
"href":"https://api.contoso.com/orders/3",
"action":"PUT",
"types":["application/x-www-form-urlencoded"]
},
{
"rel":"self",
"href":"https://api.contoso.com/orders/3",
"action":"DELETE",
"types":[]
}]
}
在此範例中 links
,陣列有一組連結。 每個連結都代表相關實體上的作業。 每個鏈接的數據都包含關聯性 (“customer)、URI (https://api.contoso.com/customers/3
)、HTTP 方法,以及支援的MIME類型。 用戶端應用程式需要此資訊才能叫用作業。
陣列也包含有關所擷取資源的自我參考資訊。 這些連結具有 自身 的關聯。
傳回的連結集可能會根據資源的狀態而變更。 超文本是 應用程式狀態引擎 的想法描述此案例。
實作版本控制
Web API 不會保持靜態。 隨著商務需求變更,會新增新的資源集合。 新增資源時,資源之間的關聯性可能會變更,而且可能會修改資源中的數據結構。 更新 Web API 來處理新的或不同的需求是一個簡單的程式,但您必須考慮這類變更對取用 Web API 的用戶端應用程式的影響。 設計及實作 Web API 的開發人員可完全控制該 API,但他們對合作夥伴組織所建置的用戶端應用程式沒有相同程度的控制。 請務必繼續支援現有的用戶端應用程式,同時允許新的用戶端應用程式使用新功能和資源。
實作版本設定的 Web API 可以指出所公開的功能和資源,用戶端應用程式可以提交導向至特定功能或資源版本的要求。 下列各節說明數種不同的方法,每個方法都有自己的優點和取捨。
沒有版本控制
這種方法最簡單,而且適用於某些內部 API。 重大變更可以表示為新的資源或新的連結。 將內容新增至現有資源可能不會顯示重大變更,因為不預期看到此內容的用戶端應用程式會忽略它。
例如,對 URI https://api.contoso.com/customers/3
的要求應該傳回單一客戶的詳細資料,其中包含用戶端應用程式預期的 id
、name
和 address
欄位:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"id":3,"name":"Fabrikam, Inc.","address":"1 Microsoft Way Redmond WA 98053"}
備註
為了簡單起見,本節中顯示的範例回應不包含HATEOAS連結。
DateCreated
如果欄位新增至客戶資源的架構,回應看起來會像這樣:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"id":3,"name":"Fabrikam, Inc.","dateCreated":"2025-03-22T12:11:38.0376089Z","address":"1 Microsoft Way Redmond WA 98053"}
如果現有的用戶端應用程式可以忽略無法辨識的欄位,可能會繼續正常運作。 同時,新的用戶端應用程式可以設計為處理這個新欄位。 不過,可能會對資源的架構進行更嚴厲的修改,包括字段移除或重新命名。 或者資源之間的關聯性可能會變更。 這些更新可能會構成中斷性變更,以防止現有的用戶端應用程式正常運作。 在這些案例中,請考慮下列其中一種方法:
URI 版本控制
每次修改 Web API 或變更資源的架構時,您都會將版本號碼新增至每個資源的 URI。 先前現有的 URI 應該會傳回符合其原始架構的資源,繼續正常運作。
例如, address
上一個範例中的欄位會重新建構為子欄位,其中包含位址的每個組成部分,例如 streetAddress
、 city
、 state
和 zipCode
。 此版本的資源可以透過包含版本號碼的 URI 公開,例如 https://api.contoso.com/v2/customers/3
:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"id":3,"name":"Fabrikam, Inc.","dateCreated":"2025-03-22T12:11:38.0376089Z","address":{"streetAddress":"1 Microsoft Way","city":"Redmond","state":"WA","zipCode":98053}}
這個版本設定機制很簡單,但取決於伺服器將要求路由至適當的端點。 不過,當網路 API 經過多次迭代而成熟,而且伺服器必須支援許多不同版本時,它可能會變得難以管理。 從純粹的觀點來看,在所有情況下,用戶端應用程式都會擷取相同的數據(客戶 3),因此 URI 不應該根據版本而有所不同。 此架構也會使HATEOAS的實作複雜化,因為所有連結都必須在其URI中包含版本號碼。
查詢字串版本控制
您可以使用附加至 HTTP 要求之查詢字串內的參數來指定資源版本,而不是提供多個 URI,例如 https://api.contoso.com/customers/3?version=2
。 如果較舊的用戶端應用程式省略它,版本參數應該預設為有意義的值,例如 1。
此方法具有一律從相同 URI 擷取相同資源的語意優勢。 不過,此方法取決於處理要求的程式代碼,以剖析查詢字串,並傳回適當的 HTTP 回應。 這個方法也會使HATEOAS的實作複雜化,與URI版本設定機制相同。
備註
某些較舊的網頁瀏覽器和 Web Proxy 不會快取在 URI 中包含查詢字串的要求回應。 未快取的回應可能會降低使用 Web API 並從舊版網頁瀏覽器內執行的 Web 應用程式效能。
標頭版本控制
您可以實作指出資源版本的自定義標頭,而不是將版本號碼附加為查詢字串參數。 此方法要求用戶端應用程式將適當的標頭新增至任何要求。 不過,如果省略版本標頭,處理用戶端要求的程式代碼可以使用預設值,例如第 1 版。
下列範例使用名為 Custom-Header 的自定義標頭。 此標頭的值表示 Web API 的版本。
版本 1:
GET https://api.contoso.com/customers/3
Custom-Header: api-version=1
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"id":3,"name":"Fabrikam, Inc.","address":"1 Microsoft Way Redmond WA 98053"}
版本 2:
GET https://api.contoso.com/customers/3
Custom-Header: api-version=2
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"id":3,"name":"Fabrikam, Inc.","dateCreated":"2025-03-22T12:11:38.0376089Z","address":{"streetAddress":"1 Microsoft Way","city":"Redmond","state":"WA","zipCode":98053}}
類似於 URI 版本設定 和 查詢字串版本設定,您必須在任何連結中包含適當的自定義標頭,才能實作 HATEOAS。
媒體類型版本化
當用戶端應用程式將 HTTP GET 要求傳送至網頁伺服器時,它應該使用 Accept 標頭來指定它可以處理的內容格式。 通常,Accept 標頭的目的是允許用戶端應用程式指定響應主體應該是 XML、JSON,還是用戶端可剖析的一些其他通用格式。 不過,您可以定義自定義媒體類型,其中包含可讓用戶端應用程式指出其預期資源版本的資訊。
下列範例顯示一個要求,指定具有 值 application/vnd.contoso.v1+json
的 Accept 標頭。 元素 vnd.contoso.v1
會向網頁伺服器指出它應該傳回第 1 版的資源。 元素 json
會指定回應主體的格式應該是 JSON:
GET https://api.contoso.com/customers/3
Accept: application/vnd.contoso.v1+json
負責處理要求的程式代碼將負責解析 Accept 標頭,並盡可能遵從該標頭的指示。 用戶端應用程式可以在 Accept 標頭中指定多種格式,這可讓網頁伺服器為回應本文選擇最適當的格式。 網頁伺服器會使用 Content-Type 標頭來確認回應本文中的數據格式:
HTTP/1.1 200 OK
Content-Type: application/vnd.contoso.v1+json; charset=utf-8
{"id":3,"name":"Fabrikam, Inc.","address":"1 Microsoft Way Redmond WA 98053"}
如果 Accept 標頭未指定任何已知的媒體類型,則網頁伺服器可以產生 HTTP 406(不可接受)回應消息,或傳回具有預設媒體類型的訊息。
此版本控制機制直接且適用於HATEOAS,它可以在資源連結中包含相關資料的MIME類型。
備註
當您選擇版本控制策略時,需注意其含意,特別是與網頁伺服器快取有關的影響。 URI 版本設定和查詢字串版本設定架構是快取易記的,因為每次相同的 URI 或查詢字串組合會參考相同的數據。
標頭版本設定和媒體類型版本設定機制通常需要更多邏輯來檢查自定義標頭或 Accept 標頭中的值。 在大規模環境中,許多使用不同版本的 Web API 的用戶端可能會導致伺服器端快取中大量重複的數據。 如果用戶端應用程式透過實作快取的 Proxy 與網頁伺服器通訊,而且只有在目前未在其快取中包含所要求數據的複本時,才會將要求轉送至網頁伺服器,這個問題可能會變得十分嚴重。
多重租戶網頁 API
多租戶 Web API 解決方案是由多個租戶共用,例如不同的組織,各有自己的使用者群組。
多租戶環境顯著影響 Web API 的設計,因為它決定了如何在單一 Web API 中的多個租戶間存取和發現資源。 設計一個針對多租戶的 API,以避免未來在實施隔離、擴展性或租戶特定的自定義時需要重構。
架構完善的 API 應該清楚定義如何在要求中識別租用戶,無論是透過子域、路徑、標頭或令牌。 此結構可確保系統內所有使用者的一致且彈性的體驗。 如需詳細資訊,請參閱 在多租戶解決方案中將請求映射到租戶。
多租戶架構會影響端點結構、請求處理、驗證和授權。 此方法也會影響 API 閘道、負載平衡器和後端服務路由和處理要求的方式。 下列策略是在 Web API 中達成多租使用者的常見方式。
使用子域或網域型隔離 (DNS 層級租用)
此方法會使用 租戶專用網域來路由傳送要求。 通配符網域使用子域來增加彈性與簡化過程。 自定義網域,可讓租使用者使用自己的網域,提供更大的控制權,並可量身打造以符合特定需求。 這兩種方法都依賴適當的 DNS 設定,包括 A
和 CNAME
記錄,將流量導向適當的基礎結構。 通配符網域可簡化設定,但自定義網域提供更品牌的體驗。
保留反向代理與後端服務之間的主機名,以避免出現 URL 重新導向等問題,並防止洩露內部 URL。 此方法可確保租使用者特定流量的正確路由,並協助保護內部基礎結構。 DNS 解析對於達成數據落地並確保法規合規性至關重要。
GET https://adventureworks.api.contoso.com/orders/3
傳遞租戶特定 HTTP 標頭
租戶資訊可以透過自定義 HTTP 標頭(如X-Tenant-ID
或X-Organization-ID
)或主機型標頭(如Host
或X-Forwarded-Host
)傳遞,也可以從 JSON Web 令牌(JWT)的宣告中擷取。 選擇取決於 API 閘道或反向 Proxy 的路由功能,標頭型解決方案需要第 7 層 (L7) 閘道來檢查每個要求。 這項需求會增加處理額外負荷,這會增加流量調整時的計算成本。 不過,基於標頭的隔離提供重要的優勢。 它可啟用集中式驗證,可簡化跨多租使用者 API 的安全性管理。 藉由使用 SDK 或 API 客戶端,可在執行期間動態管理租戶上下文,從而減少客戶端設定的複雜性。 此外,在標頭中保留租用戶上下文可以通過避免在 URI 中加入租用戶特定數據來實現更簡潔、更符合 RESTful 的 API 設計。
標頭型路由的一個重要考慮是,它會使快取複雜化,特別是當快取層完全依賴 URI 型密鑰且不考慮標頭時。 由於大多數快取機制針對 URI 查詢進行優化,因此依賴標頭可能會導致快取條目分散。 片段項目可減少快取叫用並增加後端負載。 更重要的是,如果快取層不按照標頭區分回應,它可能會將針對一個租戶的快取數據提供給另一個租戶,從而產生數據洩露的風險。
GET https://api.contoso.com/orders/3
X-Tenant-ID: adventureworks
或
GET https://api.contoso.com/orders/3
Host: adventureworks
或
GET https://api.contoso.com/orders/3
Authorization: Bearer <JWT-token including a tenant-id: adventureworks claim>
透過 URI 路徑傳遞租使用者特定資訊
此方法會在資源階層內附加租用戶識別碼,並依賴 API 閘道或反向代理伺服器,根據路徑區段來判斷適當的租用戶。 路徑型隔離很有效,但它會危害 Web API 的 RESTful 設計,並引進更複雜的路由邏輯。 它通常需要模式比對或正則表達式來剖析和規範 URI 路徑。
相反地,基於標頭的隔離會透過 HTTP 標頭以鍵值對的形式傳達租戶資訊。 這兩種方法可讓有效率的基礎結構共用降低營運成本,並提升大規模多租使用者 Web API 的效能。
GET https://api.contoso.com/tenants/adventureworks/orders/3
在 API 中啟用分散式追蹤和追蹤內容
隨著分散式系統和微服務架構成為標準, 新式架構的複雜性也會增加。 使用 Correlation-ID
、X-Request-ID
或 X-Trace-ID
標頭在 API 請求中傳播追蹤內容是達成端對端可見度的最佳做法。 此方法可讓您在要求從用戶端流向後端服務時順暢地追蹤要求。 它有助於快速識別錯誤、監視延遲,以及跨服務映射 API 相依性。
支援包含追蹤和內容資訊的 API 可增強其可檢視層級和偵錯功能。 透過啟用分散式追蹤,這些 API 可讓您更細微地了解系統行為,並讓您更輕鬆地追蹤、診斷和解決跨複雜、多服務環境的問題。
GET https://api.contoso.com/orders/3
Correlation-ID: 0f8fad5b-d9cb-469f-a165-70867728950e
HTTP/1.1 200 OK
...
Correlation-ID: 0f8fad5b-d9cb-469f-a165-70867728950e
{...}
Web API 成熟度模型
2008年,倫納德·理查森提出了現在稱為Web API的理查森成熟度模型(RMM)。 RMM 定義 Web API 的四個成熟度層級,並以 REST 原則為基礎,作為設計 Web 服務的架構方法。 在 RMM 中,隨著成熟度層級的提高,API 會變得更加 RESTful,且更緊密地遵循 REST 的原則。
層級為:
- 層級 0: 定義一個 URI,而且所有作業都是這個 URI 的 POST 要求。 簡單物件存取通訊協定 Web 服務通常在此層級。
- 層級 1: 為個別資源建立個別 URI。 此層級還不是 RESTful,但它會開始與 RESTful 設計一致。
- 層級 2: 使用 HTTP 方法來定義資源上的作業。 實際上,許多已發佈的 Web API 大致與這個層級一致。
- 層級 3: 使用超媒體(HATEOAS)。 根據 Fielding 的定義,此層級確實是 RESTful API。
OpenAPI 方案
OpenAPI 方案是由產業聯盟所建立,可跨廠商標準化 REST API 描述。 標準化規格在納入 OpenAPI 方案之前稱為 Swagger,並重新命名為 OpenAPI 規格 (OAS)。
您可能想要針對 RESTful Web API 採用 OpenAPI。 請考慮下列幾點:
OAS 隨附一組適用於 REST API 設計的有意見指導方針。 指導方針對互操作性有利,但需要您確保您的設計符合規格。
OpenAPI 推廣合約優先的方法,而不是實作優先的方法。 Contract-first 表示您先設計 API 合約 (介面),然後撰寫實作合約的程序代碼。
Swagger (OpenAPI) 之類的工具可以從 API 合約產生用戶端連結庫或檔。 如需範例,請參閱 使用 Swagger/OpenAPI ASP.NET Core Web API 檔。
後續步驟
- 請參閱在 Azure 上設計 REST API 的詳細建議。
- 查看設計及實作 Web API 時要考慮的項目 檢查清單 。
- 在 Azure 上建 置軟體即服務和多租使用者解決方案架構 。