在构建API时,咱们不可避免的会采用现有的跨平台的HTTP的交互方式与资源模型,所以若是你发现你目前的模式与咱们的标准南辕北辙,那么请咨询大家专业的API设计师以得到进一步的建议。git
URI应当包含vN
,其中N
指明版本号。基于URL的版本控制相较于其余复杂的请求头的方法会显得简单易用不少。github
URI Templateweb
/v{version}/
Exampleapi
/v1/
若是在URI中你须要考虑命名空间这个概念,那么应当选择紧邻在version
以后的第一个字段。命名空间折射出消费者对于API功能的观点,而不必定是公司自己业务逻辑层级的划分。缓存
URI Template架构
/{version}/{namespace}/
Exampleapp
/v1/vault/
URI与资源之间的关联应当保证一致性,避免出现容易引发混淆的子命名空间或者子目录的命名,这样有助于使用者可以很明晰地构造这些请求的URI。webapp
URI Templateide
/{version}/{namespace}/{resource}/{resource-id}/{sub-resource}/{sub-resource-id}
支持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 | 应当在屡次请求下具备相同的响应 |
包含任何相关联的元信息的给定资源的列表,全部的资源应当包含在items
域中,而相似于total_items
以及 total_pages
的域指明整个关于数据总体的信息。这种命名的一致性有助于客户端开发者构建面向不一样的资源集合的通用的处理函数。若是使用GET
动做进行访问,那么注意不该该影响到整个系统,而且保证除非数据发送变化不然响应消息也应该保持一致性。另外还须要注意的是,譬如日志输出这种动做不会被认为是对系统的修改。
在API客户端权限合规的状况下容许对于资源列表进行过滤操做,即并非本次都要把所有资源进行返回。另外,咱们须要提供一个简短的摘要性质的资源表述来减小带宽的消耗,通常来讲单个资源都包含较多的属性。
关于分页的操做应该来源于请求时的page
与page_size
参数,其中page_size
指明了每次请求的结果数目,page
指明了请求的是第几页。另外,响应时应当保证包含total_items
与total_pages
这两个参数,其中total_items
指示请求的集合中总的数目,total_pages
指向总的页数(total_items
/page_size
)。
Hypermedia links用于在分页的集合资源中指明请求其余页资源的便捷地址,通常来讲会包含在next
, previous
, first
, last
等等相似的命名下。
若是须要根据时间进行选择,那么须要添加start_time
或者{property_name}_after
, end_time
或者 {property_name}_before
这些查询参数。
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" } ]
}
若是返回的资源集合为空,即没有任何的资源项,此时也不该该返回404 Not Found
,而应该将items
项设置为空,而且提供一些集合的元信息,譬如total_count
设置为0。而若是是错误的请求参数应当返回404 Bad Request
。不然应该返回200 OK
来表示成功的返回值。
单个资源通常比资源集合中的对应项更详细,同时须要注意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
状态码
注意,使用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
状态码,即没有任何的返回体。
不一样于每次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操做当作幂等操做对待。所以每次删除操做都应该返回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
通常来讲,建立某个资源的请求体与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" } ]
}
当某个API Consumer自定义了Resource Identifier,那么应该使用PUT动做来建立资源,这样也能保证幂等性。
在某些状况下,咱们可能须要多个标识符来定位到某个资源,这一类资源每每是其余资源的子类。
多层的资源标识符自己对于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
当父子资源之间其实是一一映射的关系时,可使用单数形式的资源名来代表多个资源标识符的做用。这种状况下子资源每每也是父资源的一部分,即所谓的被父资源全部。不然子资源应当被放置于独立的资源集合中,而且以其余方式代表父子资源的关联。若是须要建立这种所谓的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
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。
架构设计的可扩展性
一旦这种模式被滥用了,URI的数量会急剧增加,特别是根级别的Action能够随着时间疯狂增加。一样的这也会致使路由或者对外提供服务的配置复杂度急速增加。
URI没法再被扩展,即不能再使用子资源。
可测试性: 由于缺少丰富的GET等读取类操做而使得与 Resource Collection-oriented 模式相比有较大缺陷
历史: 全部对于Action的调用应该存在某种资源中,譬如/action-resource-history
。
避免由于短暂性数据而致使资源集合模型的损害。
可用性的提高:这种Action-Oriented模式可以大大简化客户端交互内容,不过客户端并不能获益于资源自己的可读性
与上文说起的这种单纯的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" } ]
}
不少时候咱们须要对于资源进行些特定的操做或者状态修正,而这些操做是没法准确的用PUT或者PATCH进行表示。这些URI看上去有点像其余的Sub-Resources不过隐含着操做名。这个模式典型的使用场景就是当改变了某个资源的状态以后会添加些额外的反作用。另外,每每须要将资源标识符包含在URL中。并且并非每一个Action都会修改资源的状态。
通常来讲,Action的响应状态都是200 OK
以及资源自己,若是没有任何的资源状态的修正,那么应该返回204 No Content
,而且不该该附上任何的响应体。
URI Template
POST /{version}/{namespace}/{resource}/{resource-id}/{complex-operation}
Example Request
POST /v1/payments/billing-agreements/I-0LN988D3JACS/suspend
{
"note": "Suspending the agreement."
}
Example Response
204 No Content
不过须要注意的是,虽然这种模式能够改变状态,也并不意味着全部的关于资源的状态改变都要使用所谓的复杂操做模式。简单的状态的改变仍然可使用PUT/PATCH,也就是意味着混合使用Resource Collection与Complex Operation从而减小操做的数目。
Example Request (for mixed use of PUT)
PATCH /v1/payments/billing-agreements/I-0LN988D3JACS
[
{ "op": "replace", "path": "/", "value": { "description": "New Description", "shipping_address": { "line1": "2065 Hamilton Ave", "city": "San Jose", "state": "CA", "postal_code": "95125", "country_code": "US" } } }
]
该系列的操做每每能够在一次请求中处理多个creates/updates/deletes操做。这一点每每从性能与可用性方面综合考虑,这也会在影响多个资源的请求中更好地维持原子性。参考下面这个例子,capture和payment都会同时被refund操做影响。对于capture资源的PUT或者PATCH操做会隐性地影响payment资源。
URI Template
POST /{version}/{namespace}/{action}
Example Request
POST /v1/payments/captures/{capture-id}/refund
Example Response
{
"id": "0P209507D6694645N", "create_time": "2013-05-06T22:11:51Z", "update_time": "2013-05-06T22:11:51Z", "state": "completed", "amount": { "total": "110.54", "currency": "USD" }, "capture_id": "8F148933LY9388354", "parent_payment": "PAY-8PT597110X687430LKGECATA", "links": [ { "href": "https://api.sandbox.paypal.com/v1/payments/refund/0P209507D6694645N", "rel": "self", "method": "GET" }, { "href": "https://api.sandbox.paypal.com/v1/payments/payment/PAY-8PT597110X687430LKGECATA", "rel": "parent_payment", "method": "GET" }, { "href": "https://api.sandbox.paypal.com/v1/payments/capture/8F148933LY9388354", "rel": "capture", "method": "GET" } ]
}
这一个类型的复杂操做并不会保留客户端的状态或者建立新的资源。每每就是一个简单的RPC调用,而后直接获取返回值。该操做通常不会用在子资源中,由于子资源操做通常会影响父资源的状态。此时返回是建议使用200 OK
这个状态码。
URI Template
POST /{version}/{namespace}/{action}
Example Request
POST /v1/risk/evaluate-payment { "code": "h43j5k6iop" }
Example Response
200 OK { "status": "VALID" }
在使用Resource Collections的时候,最好是使用查询参数来进行集合内容的过滤。不过有时候咱们会须要一些更为复杂的查询语法,单纯的查询参数的方式会致使使用的问题或者受限于查询参数的长度。这时候,应该选择使用POST请求来指定查询参数。
若是响应体比较大的状况下应当使用分页方式,须要注意的是,Consumer应该在每次子请求的时候都使用POST方式。这样也就意味着,在POST请求体中须要维护一些查询参数。分页查询参数一样能够参考Resource Collections这一部分。一样能够适用于提供 next
, previous
, first
, last
来进行其余页的快速读取。
URI Template
POST /{version}/{namespace}/{search-resource}
Example Request
POST /v1/factory/widgets-search { "created_before":"1975-05-13", "status": "ACTIVE", "vendor": "Parts Inc." }
Example Response
200 OK { "items": [ <<lots of part objects here>> ] "links": [ { "href": "https://api.sandbox.factory.io/v1/factory/widgets-search?page=2&page_size=10", "rel": "next", "method": "POST" }, { "href": "https://api.sandbox.factory.io/v1/factory/widgets-search?page=124&page_size=10", "rel": "last", "method": "POST" }, ] }
在部分须要进行计算或者静态引用的场景下,GET会比POST请求更为合适,由于POST在HTTP层面是不会有缓存的。GET请求自己是幂等的,即不会改变资源的状态,而POST请求能够用于Complex Operations。
URI Template
GET /{version}/{namespace}/{read-only-resource}
Example Request
GET /v1/location/geocode?address=77+N.+Washington+Street%2C+Boston%2C+MA%2C+02114