來自於PayPal的RESTful API標準
之前學習過一篇文章《Google API設計指南,面向資源的設計》,有點茅塞頓開的感覺
今天又學習了一篇文章,覺得非常好,就轉載過來了,文章轉載自《來自PayPal的RestFull API標準》
原文地址:PayPal API Design Guidelines
在構建API時,我們不可避免的會採用現有的跨平臺的HTTP的互動方式與資源模型,因此如果你發現你目前的模式與我們的標準南轅北轍,那麼請諮詢你們專業的API設計師以獲得進一步的建議。
URI Components
Version:版本控制
URI應當包含vN
,其中N
指明版本號。基於URL的版本控制相較於其他複雜的請求頭的方法會顯得簡單易用很多。
-
URI Template
/v{version}/
-
Example
/v1/
Namespaces:名稱空間
如果在URI中你需要考慮名稱空間這個概念,那麼應當選擇緊鄰在version
之後的第一個欄位。名稱空間折射出消費者對於API功能的觀點,而不一定是公司本身業務邏輯層級的劃分。
-
URI Template
/{version}/{namespace}/
-
Example
/v1/vault/
Resource References:資源關聯
URI與資源之間的關聯應當保證一致性,避免出現容易引起混淆的子名稱空間或者子目錄的命名,這樣有助於使用者能夠很明晰地構造這些請求的URI。
-
URI Template
/{version}/{namespace}/{resource}/{resource-id}/{sub-resource}/{sub-resource-id}
Collection Resources
支援CRUD操作的資源被稱為是Collection Resources,往往這些資源會與POST/GET/PUT/PATCH/DELETE這些HTTP動詞緊密關聯。Collection Resources命名時應當使用複數名詞,譬如/users
,這樣可以和下面提及的Singletons進行區分。
Verb | Usage | Idempotent:冪等性 | Notes |
---|---|---|---|
GET | Read | X | |
POST | Create | 僅當使用 PayPal-Request-Id 請求頭時具有冪等性 | |
PUT | Create | 僅當在客戶端提供了資源識別符號時具有建立功能 | |
PUT | Update | X | 僅用於某個資源的全部屬性的更新,不可用於區域性 |
PATCH | Update | 使用JSON Patch 訊息格式 | |
DELETE | Delete | 應當在多次請求下具有相同的響應 |
Collection Resource:獲取資源集合/列表
包含任何相關聯的元資訊的給定資源的列表,所有的資源應當包含在items
域中,而類似於total_items
以及 total_pages
的域指明整個關於資料整體的資訊。這種命名的一致性有助於客戶端開發者構建面向不同的資源集合的通用的處理函式。如果使用GET
動作進行訪問,那麼注意不應該影響到整個系統,並且保證除非資料傳送變化否則響應訊息也應該保持一致性。另外還需要注意的是,譬如日誌輸出這種動作不會被認為是對系統的修改。
在API客戶端許可權合規的情況下允許對於資源列表進行過濾操作,即並不是本次都要把全部資源進行返回。另外,我們需要提供一個簡短的摘要性質的資源表述來減少頻寬的消耗,一般來說單個資源都包含較多的屬性。
Filtering:過濾
Paging:分頁
關於分頁的操作應該來源於請求時的page
與page_size
引數,其中page_size
指明瞭每次請求的結果數目,page
指明瞭請求的是第幾頁。另外,響應時應當保證包含total_items
與total_pages
這兩個引數,其中total_items
指示請求的集合中總的數目,total_pages
指向總的頁數(total_items
/page_size
)。
Hypermedia links:超連結
Hypermedia links用於在分頁的集合資源中指明請求其他頁資源的便捷地址,一般來說會包含在next
, previous
, first
, last
等等類似的命名下。
Time selection
如果需要根據時間進行選擇,那麼需要新增start_time
或者{property_name}_after
, end_time
或者 {property_name}_before
這些查詢引數。
Sorting:排序
sort_by
以及 sort_order
引數可以用來指明需要被排序的資源集合。一般來說sort_by
需要包含某個獨立資源名,而sort_order
應該是asc
或者desc
值。
-
URI Template
GET /{version}/{namespace}/{resource}
-
Example Request
GET /v1/vault/credit-cards
-
Example Resopnse
{
"total_items": 1,
"total_pages": 1,
"items": [
{
"id": "CARD-1SV265177X389440GKLJZIYY",
"state": "ok",
"payer_id": "user12345",
"type": "visa",
"number": "xxxxxxxxxxxx0331",
"expire_month": "11",
"expire_year": "2018",
"first_name": "Joe",
"last_name": "Shopper",
"valid_until": "2017-01-12T00:00:00Z",
"create_time": "2014-01-13T07:23:15Z",
"update_time": "2014-01-13T07:23:15Z",
"links": [
{
"href": "https://api.sandbox.paypal.com/v1/vault/credit-cards/CARD-1SV265177X389440GKLJZIYY",
"rel": "self",
"method": "GET"
},
{
"href": "https://api.sandbox.paypal.com/v1/vault/credit-cards/CARD-1SV265177X389440GKLJZIYY",
"rel": "delete",
"method": "DELETE"
},
{
"href": "https://api.sandbox.paypal.com/v1/vault/credit-cards/CARD-1SV265177X389440GKLJZIYY",
"rel": "patch",
"method": "PATCH"
}
]
}
],
"links": [
{
"href": "https://api.sandbox.paypal.com/v1/vault/credit-cards/?page_size=10&sort_by=create_time&sort_order=asc",
"rel": "first",
"method": "GET"
}
]
}
HTTP Status
如果返回的資源集合為空,即沒有任何的資源項,此時也不應該返回404 Not Found
,而應該將items
項設定為空,並且提供一些集合的元資訊,譬如total_count
設定為0。而如果是錯誤的請求引數應當返回404 Bad Request
。否則應該返回200 OK
來表示成功的返回值。
Read Single Resource
單個資源一般比資源集合中的對應項更詳細,同時需要注意GET請求不應該影響到系統。對於敏感資料的資源標識不應該是連續的或者數值型別的,另外,如果待讀取的資料是其他資料的子類,那麼應該使用不可變的字串識別符號,這樣可讀性與可除錯性都會更好。
-
URI Template
GET /{version}/{namespace}/{resource}/{resource-id}
-
Example Request
GET /v1/vault/customers/CUSTOMER-66W27667YB813414MKQ4AKDY
-
Example Response
{
"merchant_customer_id": "merchant-1",
"merchant_id": "target",
"create_time": "2014-10-10T16:10:55Z",
"update_time": "2014-10-10T16:10:55Z",
"first_name": "Kartik",
"last_name": "Hattangadi"
}
-
HTTP Status
如果指定的資源並不存在,那麼應該返回404 Not Found
狀態,否則應該返回200 OK
狀態碼
Update Single Resource
注意,使用PUT動作更新單個資源的時候需要除了需要修正的值否則保證PUT請求的值與GET響應的值保持一致性,另外對於像create_time
這樣系統自動計算的值也可以忽略。
-
URI Template
PUT /{version}/{namespace}/{resource}/{resource-id}
-
Example Request
PUT /v1/vault/customers/CUSTOMER-66W27667YB813414MKQ4AKDY
{
"merchant_customer_id": "merchant-1",
"merchant_id": "target",
"create_time": "2014-10-10T16:10:55Z",
"update_time": "2014-10-10T16:10:55Z",
"first_name": "Kartik",
"last_name": "Hattangadi"
}
-
HTTP Status
任何處理失敗的請求都應該返回400 Bad Request
,特別是如果客戶端想要更改某個只讀的欄位,也應該返回400 Bad Request
。如果具體的業務邏輯上存在校驗規則,譬如對於資料的型別、長度等等,那麼應該提供具體的操作碼說明。如果部分場景下需要客戶單與其他API進行互動或者在本次請求之外發出額外的請求,那麼應該返回422
狀態碼,詳情可以參考PPaaS Blog on this topic這篇文章。
對於其他成功的更新請求,應該返回204 No Content
狀態碼,即沒有任何的返回體。
Update Partial Single Resoure
不同於每次PUT請求中都需要更新資源的全部屬性,PACTH可以根據指定的域更新對應的屬性值,並且不會影響到其他屬性。JSON Patch 是一個推薦的資訊格式,在PayPal的幾乎所有關於PATCH的操作中都有所應用。除非客戶端的特別需要,否則每次PATCH操作的返回狀態都應該是204 No Content
,這樣從頻寬的角度,特別是在移動裝置中能夠更好地節約流量。
-
URI Template
PATCH /{version}/{namespace}/{resource}/{resource-id}
-
Example Request
PATCH /v1/notifications/webhooks/52Y53119KP6130839
[
{
"op": "replace",
"path": "/url",
"value": "https://www.yeowza.com/paypal_webhook_new_url"
}
-
Example Response
204 No Content
-
HTTP Status
和PUT請求一致。
Delete Single Resource
在刪除一個資源的時候,為了保證客戶端的可重試性,應當將DELETE操作當做冪等操作對待。因此每次刪除操作都應該返回204 No Content
狀態碼,否則如果你返回的是404 Not Found
可能會讓客戶端誤認為該資源是並不存在,而不是被刪除了。應該使用GET請求來驗證某個資源是否被成功刪除,而不應該通過DELETE請求進行驗證。
-
URI Template
DELETE /{version}/{namespace}/{resource}/{resource-id}
-
Example Request
DELETE /v1/vault/customers/CUSTOMER-66W27667YB813414MKQ4AKDY
204 No Content
Create New Resource
一般來說,建立某個資源的請求體與GET/PUT不太一致,大部分情況下API Server都會為該資源建立一個全域性的資源描述符,即使用[Create New Resource - Consumer ID]()。一旦POST請求被成功執行,也就意味著資源建立成功,那麼該資源的描述符也會被新增到資源集合的URI中。Hypermedia links提供了一種較為便捷的方式訪問新近建立的資源,可以使用rel
: self
。
-
URI Template
POST /{version}/{namespace}/{resource}
-
Example Request
POST /v1/vault/credit-cards
{
"payer_id": "user12345",
"type": "visa",
"number": "4417119669820331",
"expire_month": "11",
"expire_year": "2018",
"first_name": "Betsy",
"last_name": "Buyer",
"billing_address": {
"line1": "111 First Street",
"city": "Saratoga",
"country_code": "US",
"state": "CA",
"postal_code": "95070"
}
}
-
Example Response
201 Created
{
"id": "CARD-1MD19612EW4364010KGFNJQI",
"valid_until": "2016-05-07T00:00:00Z",
"state": "ok",
"payer_id": "user12345",
"type": "visa",
"number": "xxxxxxxxxxxx0331",
"expire_month": "11",
"expire_year": "2018",
"first_name": "Betsy",
"last_name": "Buyer",
"links": [
{
"href": "https://api.sandbox.paypal.com/v1/vault/credit-cards/CARD-1MD19612EW4364010KGFNJQI",
"rel": "self",
"method": "GET"
},
{
"href": "https://api.sandbox.paypal.com/v1/vault/credit-cards/CARD-1MD19612EW4364010KGFNJQI",
"rel": "delete",
"method": "DELETE"
}
]
}
Create New Resource - Consumer Supplied Identifier
當某個API Consumer自定義了Resource Identifier,那麼應該使用PUT動作來建立資源,這樣也能保證冪等性。
Sub-Resource Collection
在某些情況下,我們可能需要多個識別符號來定位到某個資源,這一類資源往往是其他資源的子類。
Cautions
-
多層的資源識別符號本身對於Consumer而言也是一種負擔。
-
儘可能地將具有唯一識別符號的資源或者沒必要指明父資源的資源作為First-Level Resource。
-
-
要注意使用多個資源識別符號的時候務必不能產生歧義,譬如
/{version}/{namespace}/{resource}/{resource-id}/{sub-resource-id}
這種直接將子資源識別符號放在父資源識別符號之後的做法就是不合適的,會讓Consumer迷糊。 -
實踐中這種資源的層疊巢狀不要超過兩層。
-
要保證API客戶端的可用性,如果在某個URI中維持大量的層級資源識別符號會大大增加複雜度。
-
服務端開發者需要校驗每一層級的識別符號來判斷是否具有訪問許可權,如果層級過深極易導致複雜度的陡升。
-
-
URI Templates
POST /{version}/{namespace}/{resource}/{resource-id}/{sub-resource}
GET /{version}/{namespace}/{resource}/{resource-id}/{sub-resource}
GET /{version}/{namespace}/{resource}/{resource-id}/{sub-resource}/{sub-resource-id}
PUT /{version}/{namespace}/{resource}/{resource-id}/{sub-resource}/{sub-resource-id}
DELETE /{version}/{namespace}/{resource}/{resource-id}/{sub-resource}/{sub-resource-id}
-
Examples
GET /v1/notifications/webhooks/{webhook-id}/event-types
POST /v1/factory/widgets/PART-4312/sub-assemblies
GET /v1/factory/widgets/PART-4312/sub-assemblies/INNER_COG
PUT /v1/factory/widgets/PART-4312/sub-assemblies/INNER_COG
DELETE /v1/factory/widgets/PART-4312/sub-assemblies/INNER_COG
Sub-Resource Singleton
當父子資源之間實際上是一一對映的關係時,可以使用單數形式的資源名來表明多個資源識別符號的作用。這種情況下子資源往往也是父資源的一部分,即所謂的被父資源所有。否則子資源應當被放置於獨立的資源集合中,並且以其他方式表明父子資源的關聯。如果需要建立這種所謂的Singleton子資源,應該使用PUT動作,因為PUT是冪等性的。可以使用PATCH來進行部分更新,不過千萬要注意不能使用PATCH進行建立操作。
-
URI Template
GET/PUT /{version}/{namespace}/{resource}/{resource-id}/{sub-resource}
-
Examples
GET /v1/customers/devices/DEV-FDU233FDSE213f)/vendor-information
Complex Operation
-
URI Template
POST /{namespace}/{action-resource}
所謂複雜的操作有時候也被稱為controller
或者actions
,務必要審慎地使用,只有在仔細考慮過上文提及的Resource Collection設計並不能滿足需要的時候再進行使用。可以參考section 2.6 of the RESTful Web Services Cookbook這一章節來了解更多的關於controller
的概念。複雜操作往往是與POST協同使用,並且大部分需要在URI中顯式地指明動作,譬如'activate', 'cancel', 'validate', 'accept', 以及 'deny'都是常見的操作。實際上,這種所謂Action-Oriented架構如果直接作用在跟URI中,即直接跟在名稱空間之後,就是典型的反模式,通常這種模式較為適用於跟隨在子資源之後。當某個場景是專注於動作,而不是資源的時候,應該建議適用Action-Oriented模式,並且此時應該適用POST動作,然後用某個單一的動詞指明action。
Risks
-
架構設計的可擴充套件性
-
一旦這種模式被濫用了,URI的數量會急劇增長,特別是根級別的Action可以隨著時間瘋狂增長。同樣的這也會導致路由或者對外提供服務的配置複雜度急速增長。
-
URI無法再被擴充套件,即不能再使用子資源。
-
-
可測試性: 因為缺乏豐富的GET等讀取類操作而使得與 Resource Collection-oriented 模式相比有較大缺陷
-
歷史: 所有對於Action的呼叫應該存在某種資源中,譬如
/action-resource-history
。
Benefits
-
避免因為短暫性資料而導致資源集合模型的損害。
-
可用性的提升:這種Action-Oriented模式能夠大大簡化客戶端互動內容,不過客戶端並不能獲益於資源本身的可讀性
Resource-Oriented Alternative
與上文提及的這種單純的Action-Oriented RFC風格URL相比,更好地方法就是與Resource Collection相結合,並且使用GET /{actions}
來獲取歷史記錄。這也允許未來基於資源模型的擴充套件。除此之外,這種模式也能較好地與event sourcing概念相結合。
-
URI Template
POST /{version}/{namespace}/{action}
-
Example Request
POST /v1/risk/payment-decisions
{
"code": "h43j5k6iop"
}
-
Example Response
201 Created
{
"code": "h43j5k6iop",
"status": "APPROVED",
"links": [
{
"href": "https://api.sandbox.paypal.com/v1/risk/payment-decisions/ID-FEF8EWR8E9FW)",
"rel": "self",
"method": "GET"
}
]
}
Complex Operation - Sub-Resource
很多時候我們需要對於資源進行些特定的操作或者狀態修正,而