RESTful Web API 設計
大多數現代的 Web 應用程式,都會公開用戶端可用來與應用程式互動的 API。 設計良好的 Web API 應以支援下列特性為目標:
平臺獨立性。 不論 API 是如何在內部實作,任何用戶端都應該能夠呼叫 API。 這需要使用標準通訊協定,並具有一種機制,讓用戶端和 Web 服務可以就要交換的數據格式達成一致。
服務演進。 Web API 應該要能獨立於用戶端應用程式之外而演化及新增功能。 隨著 API 的發展,現有的用戶端應用程式即使不進行修改,也應該能夠繼續運作。 所有功能應該要可供探索,讓用戶端應用程式得以充分運用。
本指南說明設計 Web API 時應考慮的問題。
什麼是 REST?
2000年,Roy Fielding 提出了代表狀態轉移(REST)作為設計Web服務的架構方法。 REST 是建置以超媒體為基礎的分散式系統的架構樣式。 REST 與任何基礎通訊協議無關,不一定系結至 HTTP。 不過,最常見的 REST API 實作會使用 HTTP 作為應用程式通訊協定,本指南著重於設計適用於 HTTP 的 REST API。
REST over HTTP 的主要優點是它會使用開放式標準,而且不會將 API 或用戶端應用程式的實作系結至任何特定實作。 例如,REST Web 服務可以用 ASP.NET 撰寫,用戶端應用程式可以使用任何語言或工具組來產生 HTTP 要求並剖析 HTTP 回應。
以下是使用 HTTP 之 RESTful API 的一些主要設計原則:
REST API 是針對 資源所設計,這些資源是用戶端可存取的任何對象、數據或服務。
資源具有 標識碼,這是可唯一識別該資源的 URI。 例如,特定客戶訂單的 URI 可能是:
https://adventure-works.com/orders/1
用戶端會藉由交換 資源的表示 法來與服務互動。 許多 Web API 都會使用 JSON 作為交換格式。 例如,上述 URI 的 GET 要求可能會傳回此回應本文:
{"orderId":1,"orderValue":99.90,"productId":1,"quantity":1}
REST API 使用統一介面,有助於分離客戶端和服務實作。 針對以 HTTP 建置的 REST API,統一介面包含使用標準 HTTP 動詞來執行資源作業。 最常見的作業是 GET、POST、PUT、PATCH 和 DELETE。
REST API 使用無狀態要求模型。 HTTP 要求應該獨立且可能依任何順序發生,因此在要求之間保留暫時性狀態資訊不可行。 儲存資訊的唯一位置是資源本身,而且每個要求都應該是不可部分完成的作業。 此條件約束可讓 Web 服務高度擴充,因為不需要保留用戶端與特定伺服器之間的任何親和性。 任何伺服器都可以處理來自任何用戶端的任何要求。 也就是說,其他因素可以限制延展性。 例如,許多 Web 服務都會寫入後端數據存放區,這可能會難以向外延展。如需相應放大數據存放區策略的詳細資訊,請參閱 水準、垂直和功能性數據分割。
REST API 是由表示法中包含的超媒體連結所驅動。 例如,下列顯示訂單的 JSON 表示法。 其中包含可取得或更新與訂單相關聯的客戶連結。
{ "orderID":3, "productID":2, "quantity":4, "orderValue":16.60, "links": [ {"rel":"product","href":"https://adventure-works.com/customers/3", "action":"GET" }, {"rel":"product","href":"https://adventure-works.com/customers/3", "action":"PUT" } ] }
2008 年,Leonard Richardson 為 Web API 提出了下列 成熟度模型 :
- 層級 0:定義一個 URI,而所有作業都是此 URI 的 POST 要求。
- 層級 1:為個別資源建立個別 URI。
- 層級 2:使用 HTTP 方法來定義資源上的作業。
- 層級 3:使用超媒體 (HATEOAS,如下所述)。
層級 3 會根據 Fielding 的定義對應至真正的 RESTful API。 實際上,許多已發佈的 Web API 落在層級 2 左右。
組織資源周圍的 API 設計
將焦點放在 Web API 公開商業實體。 例如,在電子商務系統中,主要實體可能是客戶和訂單。 可藉由傳送包含訂單資訊的 HTTP POST 要求來建立訂單。 HTTP 回應會指出訂購成功與否。 如果可能的話,資源 URI 應該要依據名詞 (資源) 而不是動詞 (在資源上的作業)。
https://adventure-works.com/orders // Good
https://adventure-works.com/create-order // Avoid
資源不需要以單一實體數據項為基礎。 例如,訂單資源可能會在內部實作為關係資料庫中的數個數據表,但以單一實體的形式呈現給用戶端。 避免建立只鏡像資料庫內部結構的 API。 REST 的目的是建立實體模型,以及應用程式可以在這些實體上執行的作業。 用戶端不應該公開至內部實作。
實體通常會分組成集合(訂單、客戶)。 集合是與集合內專案不同的資源,而且應該有自己的 URI。 例如,下列 URI 可能代表訂單集合:
https://adventure-works.com/orders
將 HTTP GET 要求傳送至集合 URI 會擷取集合中的項目清單。 集合中的每個專案也都有自己的唯一 URI。 對專案 URI 的 HTTP GET 要求會傳回該專案的詳細數據。
在 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。 這類 API 可能需要用戶端應用程式傳送多個要求,以尋找它所需的所有資料。 相反地,您可能想要將數據反正規化,並將相關信息合併成可透過單一要求擷取的較大資源。 不過,您必須將此方法與用戶端不需要擷取數據的額外負荷進行平衡。 擷取大型物件可能會增加要求的延遲,併產生額外的頻寬成本。 如需這些效能反模式的詳細資訊,請參閱 Chatty I/O 和 外部擷取。
避免在 Web API 與基礎數據源之間引入相依性。 例如,如果您的數據儲存在關係資料庫中,Web API 就不需要將每個數據表公開為資源的集合。 事實上,這可能是一個糟糕的設計。 相反地,請將 Web API 視為資料庫的抽象概念。 如有必要,請在資料庫與 Web API 之間引入對應層。 如此一來,用戶端應用程式就會與基礎資料庫配置的變更隔離。
最後,可能無法將 Web API 所實作的每個作業對應至特定資源。 您可以透過叫用函式的 HTTP 要求來處理這類 非資源 案例,並以 HTTP 回應訊息的形式傳回結果。 例如,實作新增和減去等簡單計算機作業的 Web API 可以提供 URI,以虛擬資源的形式公開這些作業,並使用查詢字串來指定所需的參數。 例如,URI /add?operand1=99&operand2=1 的 GET 要求會傳回包含值 100 的本文回應訊息。 不過,請謹慎使用這些形式的 URI。
在 HTTP 方法方面定義 API 作業
HTTP 通訊協定會定義一些方法,這些方法會將語意意義指派給要求。 大部分 RESTful Web API 所使用的常見 HTTP 方法如下:
- GET 會擷取指定 URI 的資源表示法。 回應訊息的本文包含所要求資源的詳細數據。
- POST 會在指定的 URI 上建立新的資源。 要求訊息的本文會提供新資源的詳細數據。 請注意,POST 也可以用來觸發實際上不會建立資源的作業。
- PUT 會建立或取代位於指定 URI 的資源。 要求訊息的本文會指定要建立或更新的資源。
- PATCH 會執行資源的部分更新。 要求本文會指定要套用至資源的變更集。
- DELETE 會移除位於指定 URI 的資源。
特定要求的效果應該取決於資源是集合還是個別專案。 下表摘要說明大部分 RESTful 實作使用電子商務範例採用的常見慣例。 並非所有的要求都可以實作,這取決於特定案例。
資源 | POST | 取得 | PUT | DELETE |
---|---|---|---|---|
/客戶 | 建立新的客戶 | 擷取所有客戶 | 大量更新客戶 | 拿掉所有客戶 |
/customers/1 | 錯誤 | 擷取客戶 1 的詳細數據 | 如果存在,請更新客戶 1 的詳細數據 | 拿掉客戶 1 |
/customers/1/orders | 為客戶1建立新訂單 | 擷取客戶 1 的所有訂單 | 客戶 1 的訂單大量更新 | 拿掉客戶 1 的所有訂單 |
POST、PUT 和 PATCH 之間的差異可能會造成混淆。
POST 要求會建立資源。 伺服器會指派新資源的 URI,並將該 URI 傳回給用戶端。 在 REST 模型中,您經常將 POST 要求套用至集合。 新的資源會新增至集合。 POST 要求也可以用來提交數據以處理至現有的資源,而不需要建立任何新的資源。
PUT 要求會建立資源 或 更新現有的資源。 用戶端會指定資源的 URI。 要求本文包含資源的完整表示法。 如果已有此 URI 的資源存在,則會加以取代。 否則,如果伺服器支援,則會建立新的資源。 PUT 要求最常套用至個別項目的資源,例如特定客戶,而不是集合。 伺服器可能支援更新,但不支援透過PUT建立。 是否支援透過PUT建立,取決於用戶端是否可以在資源存在之前有意義地將URI指派給資源。 如果沒有,請使用POST來建立資源和PUT或 PATCH來更新。
PATCH 要求會對 現有資源執行部分更新 。 用戶端會指定資源的 URI。 要求本文會指定要套用至資源的一組 變更 。 這比使用 PUT 更有效率,因為用戶端只會傳送變更,而不是資源的整個表示法。 技術上 PATCH 也可以建立新的資源(藉由指定一組“null” 資源的更新),如果伺服器支援此專案。
PUT 要求必須是等冪。 如果用戶端多次提交相同的 PUT 要求,結果應該一律相同(相同的資源會使用相同的值修改)。 POST 和 PATCH 要求不保證為等冪。
符合 HTTP 語意
本節說明設計符合 HTTP 規格之 API 的一些典型考慮。 不過,它並未涵蓋每個可能的詳細數據或案例。 如有疑問,請參閱 HTTP 規格。
媒體類型
如先前所述,客戶端和伺服器會交換資源的表示法。 例如,在 POST 要求中,要求本文包含要建立的資源表示。 在 GET 要求中,回應本文包含所擷取資源的表示法。
在 HTTP 通訊協定中,格式是透過使用 媒體類型來指定,也稱為 MIME 類型。 對於非二進位數據,大部分的 Web API 都支援 JSON(媒體類型 = application/json
) 和可能 XML (媒體類型 = application/xml
)。
要求或回應中的 Content-Type 標頭會指定表示法的格式。 以下是包含 JSON 資料的 POST 要求範例:
POST https://adventure-works.com/orders HTTP/1.1
Content-Type: application/json; charset=utf-8
Content-Length: 57
{"Id":1,"Name":"Gizmo","Category":"Widgets","Price":1.99}
如果伺服器不支援媒體類型,它應該會傳回 HTTP 狀態代碼 415(不支援的媒體類型)。
用戶端要求可以包含 Accept 標頭,其中包含用戶端將在回應訊息中從伺服器接受的媒體類型清單。 例如:
GET https://adventure-works.com/orders/2 HTTP/1.1
Accept: application/json
如果伺服器不符合列出的任何媒體類型,它應該會傳回 HTTP 狀態代碼 406 (不可接受)。
GET 方法
成功的 GET 方法通常會傳回 HTTP 狀態代碼 200 (確定)。 如果找不到資源,方法應該會傳回 404 (找不到)。
如果要求已完成,但 HTTP 回應中未包含回應本文,則它應該會傳回 HTTP 狀態代碼 204 (沒有內容):例如,搜尋作業不會產生任何相符專案,可能會使用此行為來實作。
POST 方法
如果 POST 方法建立新的資源,則會傳回 HTTP 狀態代碼 201(已建立)。 新資源的 URI 會包含在回應的 Location 標頭中。 回應本文包含資源的表示。
如果方法有一些處理但未建立新的資源,則方法可以傳回 HTTP 狀態代碼 200,並在回應本文中包含作業的結果。 或者,如果沒有傳回任何結果,方法可以傳回 HTTP 狀態代碼 204(沒有內容),且沒有回應本文。
如果客戶端將無效的數據放入要求中,伺服器應該會傳回 HTTP 狀態代碼 400 (不正確的要求)。 回應本文可以包含有關錯誤的其他資訊,或提供更多詳細數據之 URI 的連結。
PUT 方法
如果 PUT 方法建立新的資源,它會傳回 HTTP 狀態代碼 201 (已建立),如同 POST 方法。 如果方法更新現有的資源,則會傳回 200 (確定) 或 204 (沒有內容)。 在某些情況下,可能無法更新現有的資源。 在此情況下,請考慮傳回 HTTP 狀態代碼 409 (衝突)。
請考慮實作大量 HTTP PUT 作業,以批處理集合中的多個資源更新。 PUT 要求應指定集合的 URI,而要求本文應指定要修改之資源的詳細數據。 這種方法有助於減少閒聊並改善效能。
PATCH 方法
透過 PATCH 要求,用戶端會以修補程式檔的形式,將一組更新傳送至現有的資源。 伺服器會處理修補程式檔以執行更新。 修補程式檔不會描述整個資源,只有一組要套用的變更。 PATCH 方法的規格 (RFC 5789) 不會定義修補程式檔的特定格式。 格式必須從要求中的媒體類型推斷。
JSON 可能是 Web API 最常見的數據格式。 有兩種以 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 狀態碼 |
---|---|
不支援修補程式檔格式。 | 415 (不支援的媒體類型) |
格式不正確的修補程序檔。 | 400 (錯誤的要求) |
修補程式檔有效,但變更無法套用至目前狀態的資源。 | 409 (衝突) |
DELETE 方法
如果刪除作業成功,Web 伺服器應該回應 HTTP 狀態代碼 204 (沒有內容),指出進程已成功處理,但回應本文不包含任何進一步資訊。 如果資源不存在,Web 伺服器可以傳回 HTTP 404 (找不到)。
非同步作業
有時候 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
如需如何實作此方法的詳細資訊,請參閱 提供長時間執行要求的 異步支援和 異步要求-回復模式。
訊息本文中的空白集合
每當成功回應的本文是空的時,狀態代碼應該是 204 (沒有內容)。 對於空白集合,例如響應篩選的要求,且沒有項目,狀態代碼應仍為 204(無內容),而不是 200 (確定)。
篩選和分頁數據
透過單一 URI 公開資源集合可能會導致應用程式只擷取一部分資訊時擷取大量數據。 例如,假設用戶端應用程式需要尋找所有訂單,其成本超過特定值。 它可能會從 /orders URI 擷取所有訂單,然後在客戶端篩選這些訂單。 顯然,此程式效率很高。 它浪費了裝載 Web API 之伺服器上的網路頻寬和處理能力。
相反地,API 可以允許在 URI 的查詢字串中傳遞篩選,例如 /orders?minCost=n。 接著,Web API 會負責剖析及處理 minCost
查詢字串中的 參數,並在伺服器端傳回篩選的結果。
對集合資源的 GET 要求可能會傳回大量的專案。 您應該設計 Web API 來限制任何單一要求所傳回的數據量。 請考慮支持查詢字串,以指定要擷取的項目數目上限,以及集合中的起始位移。 例如:
/orders?limit=25&offset=50
也請考慮對傳回的項目數目施加上限,以協助防止拒絕服務攻擊。 為了協助用戶端應用程式,傳回編頁數據的 GET 要求也應該包含某種形式的元數據,指出集合中可用的資源總數。
您可以藉由提供接受功能變數名稱做為值的排序參數,例如 /orders?sort=ProductID,使用類似的策略來排序數據。 不過,此方法可能會對快取產生負面影響,因為查詢字串參數構成許多快取實作作為快取數據索引鍵所使用之資源識別碼的一部分。
如果每個專案包含大量數據,您可以擴充此方法來限制每個項目傳回的欄位。 例如,您可以使用查詢字串參數來接受以逗號分隔的欄位清單,例如 /orders?fields=ProductID,Quantity。
為查詢字串中的所有選擇性參數提供有意義的預設值。 例如,如果您實作分頁,請將 limit
參數設定為 10,並將 offset
參數設定為 0,如果您實作排序,請將排序參數設定為資源的索引鍵,並在支援投影時將參數設定 fields
為資源中的所有欄位。
支援大型二進位資源的部分回應
資源可能包含大型二進位欄位字段,例如檔案或影像。 若要克服不可靠和間歇性連線所造成的問題,並改善回應時間,請考慮讓這類資源以區塊擷取。 若要這樣做,Web API 應該支援大型資源的 GET 要求 Accept-Ranges 標頭。 此標頭表示 GET 作業支援部分要求。 用戶端應用程式可以提交 GET 要求,以傳回資源子集,指定為位元組範圍。
此外,請考慮實作這些資源的 HTTP HEAD 要求。 HEAD 要求與 GET 要求類似,不同之處在於它只會傳回描述資源的 HTTP 標頭,其中包含空的訊息本文。 用戶端應用程式可以發出 HEAD 要求,以判斷是否使用部分 GET 要求來擷取資源。 例如:
HEAD https://adventure-works.com/products/10?fields=productImage HTTP/1.1
以下是回應訊息範例:
HTTP/1.1 200 OK
Accept-Ranges: bytes
Content-Type: image/jpeg
Content-Length: 4580
Content-Length 標頭會提供資源的總大小,而 Accept-Ranges 標頭表示對應的 GET 作業支援部分結果。 用戶端應用程式可以使用這項資訊,以較小的區塊擷取映像。 第一個要求會使用 Range 標頭擷取前 2500 個字節:
GET https://adventure-works.com/products/10?fields=productImage HTTP/1.1
Range: bytes=0-2499
回應消息表示這是傳回 HTTP 狀態代碼 206 的部分回應。 Content-Length 標頭會指定訊息本文中傳回的實際位元組數目(而非資源大小),而 Content-Range 標頭會指出這是哪一個資源部分(4580 中的位元組 0-2499):
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://adventure-works.com/customers/3",
"action":"GET",
"types":["text/xml","application/json"]
},
{
"rel":"customer",
"href":"https://adventure-works.com/customers/3",
"action":"PUT",
"types":["application/x-www-form-urlencoded"]
},
{
"rel":"customer",
"href":"https://adventure-works.com/customers/3",
"action":"DELETE",
"types":[]
},
{
"rel":"self",
"href":"https://adventure-works.com/orders/3",
"action":"GET",
"types":["text/xml","application/json"]
},
{
"rel":"self",
"href":"https://adventure-works.com/orders/3",
"action":"PUT",
"types":["application/x-www-form-urlencoded"]
},
{
"rel":"self",
"href":"https://adventure-works.com/orders/3",
"action":"DELETE",
"types":[]
}]
}
在此範例中 links
,陣列有一組連結。 每個連結都代表相關實體上的作業。 每個鏈接的數據都包含關聯性 (“customer)、URI (https://adventure-works.com/customers/3
)、HTTP 方法,以及支援的MIME類型。 這是用戶端應用程式需要能夠叫用作業的所有資訊。
數位也 links
包含已擷取之資源本身的自我參考資訊。 這些有關聯 性自我。
傳回的連結集可能會根據資源的狀態而變更。 這就是超文本是「應用程式狀態的引擎」所代表的意義。
進行 RESTful web API 的版本設定
Web API 不太可能保持靜態。 隨著商務需求變更新的資源集合,資源之間的關聯性可能會變更,而且可能會修改資源中的數據結構。 雖然更新 Web API 來處理新的或不同的需求是一個相對簡單的程式,但您必須考慮這類變更對取用 Web API 的用戶端應用程式所產生的影響。 問題是,雖然設計及實作 Web API 的開發人員可以完全控制該 API,但開發人員對用戶端應用程式的控制程度不相同,而用戶端應用程式可能是由遠端操作的第三方組織所建置。 主要命令是讓現有的用戶端應用程式繼續維持不變運作,同時允許新的用戶端應用程式利用新功能和資源。
版本控制可讓 Web API 指出所公開的功能和資源,而用戶端應用程式可以提交導向至特定功能或資源版本的要求。 下列各節說明數種不同的方法,每個方法都有自己的優點和取捨。
沒有版本控制
這是最簡單的方法,一些內部 API 可能可以接受。 重大變更可以表示為新的資源或新的連結。 將內容新增至現有資源可能不會顯示重大變更,因為未預期看到此內容的用戶端應用程式會忽略它。
例如,對 URI https://adventure-works.com/customers/3
的要求應該會傳回單一客戶的詳細數據,其中包含 id
用戶端應用程式所預期的 、 name
和 address
字段:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"id":3,"name":"Contoso LLC","address":"1 Microsoft Way Redmond WA 98053"}
注意
為了簡單起見,本節所示的範例回應不包含HATEOAS連結。
DateCreated
如果欄位新增至客戶資源的架構,則響應看起來會像這樣:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"id":3,"name":"Contoso LLC","dateCreated":"2014-09-04T12:11:38.0376089Z","address":"1 Microsoft Way Redmond WA 98053"}
如果現有的用戶端應用程式能夠忽略無法辨識的欄位,則現有的用戶端應用程式可能會繼續正常運作,而新的用戶端應用程式則設計來處理這個新字段。 不過,如果對資源的架構進行更徹底的變更(例如移除或重新命名字段),或資源之間的關聯性變更,則這些變更可能會構成中斷性變更,以防止現有的用戶端應用程式正常運作。 在這些情況下,您應該考慮下列其中一種方法。
URI 版本控制
每次修改 Web API 或變更資源的架構時,您都會將版本號碼新增至每個資源的 URI。 先前現有的 URI 應該會繼續像之前一樣運作,並傳回符合其原始架構的資源。
擴充前一個範例,如果將 address
欄位重新建構為包含位址每個組成部分的子欄位(例如 streetAddress
、 city
、 state
和 zipCode
),這個版本的資源可以透過包含版本號碼的 URI 公開,例如 https://adventure-works.com/v2/customers/3
:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"id":3,"name":"Contoso LLC","dateCreated":"2014-09-04T12:11:38.0376089Z","address":{"streetAddress":"1 Microsoft Way","city":"Redmond","state":"WA","zipCode":98053}}
這個版本設定機制非常簡單,但取決於伺服器將要求路由至適當的端點。 不過,當 Web API 透過數個反覆專案成熟,而且伺服器必須支援許多不同的版本時,它可能會變得不笨拙。 此外,從純粹的觀點來看,在所有情況下,用戶端應用程式都會擷取相同的數據(客戶 3),因此 URI 不應該根據版本而真正不同。 此配置也會使HATEOAS的實作複雜化,因為所有連結都必須在其URI中包含版本號碼。
查詢字串版本控制
您可以使用附加至 HTTP 要求之查詢字串內的 參數來指定資源版本,而不是提供多個 URI,例如 https://adventure-works.com/customers/3?version=2
。 如果舊版用戶端應用程式省略版本參數,版本參數應該預設為有意義的值,例如 1。
此方法具有一律從相同 URI 擷取相同資源的語意優勢,但這取決於處理要求的程式代碼來剖析查詢字元串,並傳回適當的 HTTP 回應。 此方法也遭受與 URI 版本控制機制實作 HATEOAS 相同的複雜問題。
注意
某些較舊的網頁瀏覽器和 Web Proxy 不會快取包含 URI 中查詢字串的要求回應。 這可能會降低使用 Web API 且從這類網頁瀏覽器內執行的 Web 應用程式效能。
標頭版本控制
您可以實作指出資源版本的自定義標頭,而不是將版本號碼附加為查詢字串參數。 此方法要求用戶端應用程式將適當的標頭新增至任何要求,不過如果省略版本標頭,處理用戶端要求的程式代碼可能會使用預設值 (第 1 版)。 下列範例使用名為 Custom-Header 的自定義標頭。 此標頭的值表示 Web API 的版本。
版本 1:
GET https://adventure-works.com/customers/3 HTTP/1.1
Custom-Header: api-version=1
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"id":3,"name":"Contoso LLC","address":"1 Microsoft Way Redmond WA 98053"}
版本 2:
GET https://adventure-works.com/customers/3 HTTP/1.1
Custom-Header: api-version=2
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"id":3,"name":"Contoso LLC","dateCreated":"2014-09-04T12:11:38.0376089Z","address":{"streetAddress":"1 Microsoft Way","city":"Redmond","state":"WA","zipCode":98053}}
如同上述兩種方法,實作HATEOAS需要在任何連結中包含適當的自定義標頭。
媒體類型版本控制
當用戶端應用程式將 HTTP GET 要求傳送至網頁伺服器時,它應該規定可以使用 Accept 標頭處理的內容格式,如本指南稍早所述。 Accept 標頭的用途通常是允許用戶端應用程式指定響應主體應該是 XML、JSON,還是用戶端可剖析的一些其他通用格式。 不過,可以定義自定義媒體類型,其中包含可讓用戶端應用程式指出所預期的資源版本的資訊。
下列範例顯示一個要求,指定具有 application/vnd.adventure-works.v1+json 值之 Accept 標頭的要求。 vnd.adventure-works.v1 元素會向網頁伺服器指出它應該傳回資源第 1 版,而 json 元素則指定回應本文的格式應該是 JSON:
GET https://adventure-works.com/customers/3 HTTP/1.1
Accept: application/vnd.adventure-works.v1+json
處理要求的程式代碼會負責處理 Accept 標頭,並盡可能接受它(用戶端應用程式可以在 Accept 標頭中指定多種格式,在此情況下,網頁伺服器可以選擇最適當的回應本文格式)。 網頁伺服器會使用 Content-Type 標頭來確認回應本文中的數據格式:
HTTP/1.1 200 OK
Content-Type: application/vnd.adventure-works.v1+json; charset=utf-8
{"id":3,"name":"Contoso LLC","address":"1 Microsoft Way Redmond WA 98053"}
如果 Accept 標頭未指定任何已知的媒體類型,網頁伺服器可能會產生 HTTP 406(不可接受)回應消息,或傳回具有預設媒體類型的訊息。
這種方法可以說是最純粹的版本設定機制,而且自然適用於HATEOAS,這可以包含資源鏈接中相關數據的MIME類型。
注意
當您選取版本控制策略時,也應該考慮效能的影響,特別是網頁伺服器上的快取。 URI 版本設定和查詢字串版本設定設定對快取易記,因為每次相同的 URI/查詢字串組合都會參考相同的數據。
標頭版本設定和媒體類型版本設定機制通常需要額外的邏輯來檢查自定義標頭或 Accept 標頭中的值。 在大規模環境中,許多使用不同版本的 Web API 的用戶端可能會導致伺服器端快取中大量重複的數據。 如果用戶端應用程式透過實作快取的 Proxy 與網頁伺服器通訊,而且只有在目前未在其快取中保存所要求數據的複本時,才會將要求轉送至網頁伺服器,此問題可能會變得嚴重。
開啟 API 方案
開放式 API 方案 是由產業聯盟所建立,可跨廠商標準化 REST API 描述。 作為此計劃的一部分,Swagger 2.0 規格已重新命名為 OpenAPI 規格 (OAS),並納入 Open API 方案。
您可能想要針對 Web API 採用 OpenAPI。 考慮事項:
OpenAPI 規格隨附一組關於 REST API 設計方式的指導方針。 這具有互操作性的優點,但在設計 API 以符合規格時需要更小心。
OpenAPI 會提升合約優先方法,而不是實作優先的方法。 合約優先表示您先設計 API 合約(介面),然後撰寫實作合約的程式代碼。
Swagger 之類的工具可以從 API 合約產生用戶端連結庫或檔。 例如,請參閱 使用 Swagger ASP.NET Web API 說明頁面。
下一步
Microsoft Azure REST API 指導方針。 在 Azure 上設計 REST API 的詳細建議。
Web API 檢查清單。 設計及實作 Web API 時要考慮的實用專案清單。
開啟 API 方案。 Open API 上的文件和實作詳細數據。