来自于PayPal的RESTful API标准

version 版本控制

URI应当包含vN,其中N指明版本号。基于URL的版本控制相较于其他复杂的请求头的方法会显得简单易用很多。

/v{version}/

例如:

/v1/

Namespaces 命名空间

命名空间折射出消费者对于API功能的观点,而不一定是公司本身业务逻辑层级的划分。

/{version}/{namespace}/

例如:

/v1/vault/

Resource References 资源关联

URI与资源之间的关联应当保证一致性,避免出现容易引起混淆的子命名空间或者子目录的命名,这样有助于使用者能够很明晰地构造这些请求的URI。

/{version}/{namespace}/{resource}/{resource-id}/{sub-resource}/{sub-resource-id}

Collection Resources 集合资源

支持CRUD操作的资源被称为是Collection Resources,往往这些资源会与POST/GET/PUT/PATCH/DELETE这些HTTP动词紧密关联。Collection Resources命名时应当使用复数名词,譬如/users。

获取资源集合/列表

在拥有相应权限的情况下,允许对资源列表进行过滤操作,即并不是本次都要把全部资源进行返回。另外,我们需要提供一个简短的摘要性质的资源表述来减少带宽的消耗,一般来说单个资源都包含较多的属性。

分页
关于分页的操作应该来源于请求时的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值。

GET /{version}/{namespace}/{resource}

例如:

GET /v1/vault/credit-cards

响应

"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。而如果是错误的请求参数应当返回400 Bad Request。否则应该返回200 OK来表示成功的返回值。

读取单个资源

单个资源一般比资源集合中的对应项更详细,同时需要注意GET请求不应该影响到系统。对于敏感数据的资源标识不应该是连续的或者数值类型的,另外,如果待读取的数据是其他数据的子类,那么应该使用不可变的字符串标识符,这样可读性与可调试性都会更好。

GET /{version}/{namespace}/{resource}/{resource-id}

例如:

GET /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 :
如果指定的资源并不存在,那么应该返回404 Not Found状态,否则应该返回200 OK状态码。注意这里单个资源的请求如果找不到可以返回404。而上文提到的集合资源如果没有数据,则需要返回一组空的结果。

更新单个资源

使用PUT请求更新单个资源,请求体中除了需要更新的值,其他值需要保持与GET请求结果一致。其中像create_time这种由系统自动生成的字段则可以忽略。

PUT /{version}/{namespace}/{resource}/{resource-id}

请求体:

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请求更新单个资源的全部属性,使用PATCH可以根据指定的字段更新资源的属性值,而不影响到其他值。JSON Patch 是一个推荐的信息格式,在PayPal的几乎所有关于PATCH的操作中都有所应用。除非客户端的特别需要,否则每次PATCH操作的返回状态都应该是204 No Content,这样从带宽的角度,特别是在移动设备中能够更好地节约流量。

PATCH /{version}/{namespace}/{resource}/{resource-id}
PATCH /v1/notifications/webhooks/52Y53119KP6130839
[
    {
        "op": "replace",
        "path": "/url",
        "value": "https://www.yeowza.com/paypal_webhook_new_url"
    }
]

返回码:

204 No Content

删除单个资源

在删除一个资源的时候,为了保证客户端的可重试性,应当将DELETE操作当做幂等操作对待。因此每次删除操作都应该返回204 No Content状态码,否则如果你返回的是404 Not Found可能会让客户端误认为该资源是并不存在,而不是被删除了。应该使用GET请求来验证某个资源是否被成功删除,而不应该通过DELETE请求进行验证。

DELETE /{version}/{namespace}/{resource}/{resource-id}
DELETE /v1/vault/customers/CUSTOMER-66W27667YB813414MKQ4AKDY
204 No Content

创建新的资源

一般来说,创建某个资源的请求体与GET/PUT不太一致,大部分情况下API Server都会为该资源创建一个全局的资源描述符,即使用Create New Resource - Consumer ID。一旦POST请求被成功执行,也就意味着资源创建成功,那么该资源的描述符也会被添加到资源集合的URI中。Hypermedia links提供了一种较为便捷的方式访问新近创建的资源,可以使用rel: self。

POST /{version}/{namespace}/{resource}

请求示例:

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"
}

}

响应:

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调用者提供资源标识符

当一个API调用者定义资源标识符时,为了保证操作的幂等性,应当使用PUT操作。
如果创建资源成功则返回201+response body。当一个以存在的资源被更新则返回204+response body。

子集合

在某些情况下,我们可能需要多个标识符来定位到某个资源,这一类资源往往是其他资源的子类。

注意事项:

  • 多层的资源标识符本身对于客户端开发人员而言也是一种负担。

    尽可能地将具有唯一标识符的资源或者没必要指明父资源的资源作为First-Level Resource。

  • 要注意使用多个资源标识符的时候务必不能产生歧义,譬如/{version}/{namespace}/{resource}/{resource-id}/{sub-resource-id}这种直接将子资源标识符放在父资源标识符之后的做法就是不合适的,会让调用者困惑。

  • 实践中这种资源的层叠嵌套不要超过两层。

    要保证API客户端的可用性,如果在某个URI中维持大量的层级资源标识符会大大增加复杂度。
    服务端开发者需要校验每一层级的标识符来判断是否具有访问权限,如果层级过深极易导致复杂度的陡升。

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}

示例:

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

复杂操作

复杂操作往往是与POST协同使用,并且大部分需要在URI中显式地指明动作,譬如’activate’, ‘cancel’, ‘validate’, ‘accept’, 以及 ‘deny’都是常见的操作。实际上,这种所谓Action-Oriented架构如果直接作用在跟URI中,即直接跟在命名空间之后,就是典型的反模式,通常这种模式较为适用于跟随在子资源之后。当某个场景是专注于动作,而不是资源的时候,应该建议适用Action-Oriented模式,并且此时应该适用POST动作,然后用某个单一的动词指明action。

风险

架构设计的可扩展性,一旦这种模式被滥用,URI的数量会急剧增长,也会导致路由或者对外提供服务的配置复杂度急速增长;URI无法再被扩展,即无法再使用子资源。
可测试性,缺乏丰富的GET等读取类操作。
历史,所有对于Action的调用应该存在在某种资源中。譬如/action-resource-history

好处

避免因为短暂性数据而导致资源集合模型的损害。
可用性提升:大大简化客户端交互内容,不过客户端并不能获益于资源本身的可读性。

与面向资源相结合

上文是面向Action风格的URI,更好的方式是与资源相结合,并使用GET /{actions}来获取历史记录。

POST /{version}/{namespace}/{action}

示例:

POST /v1/risk/payment-decisions
{"code":"h43j5k6iop"}

响应:
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,并且不应该附上任何的响应体。

POST /{version}/{namespace}/{resource}/{resource-id}/{complex-operation}

示例:

POST /v1/payments/billing-agreements/I-0LN988D3JACS/suspend
{
"note": "Suspending the agreement."
}

响应:
204 No Content

不过需要注意的是,虽然这种模式可以改变状态,也并不意味着所有的关于资源的状态改变都要使用所谓的复杂操作模式。简单的状态的改变仍然可以使用PUT/PATCH,也就是意味着混合使用Resource Collection与Complex Operation从而减少操作的数目。

请求合并

该系列的操作往往可以在一次请求中处理多个creates/updates/deletes操作。这一点往往从性能与可用性方面综合考虑,这也会在影响多个资源的请求中更好地维持原子性。参考下面这个例子,capture和payment都会同时被refund操作影响。对于capture资源的PUT或者PATCH操作会隐性地影响payment资源。

POST /{version}/{namespace}/{action}

示例:

POST /v1/payments/captures/{capture-id}/refund

响应:

{

"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这个状态码。

POST /{version}/{namespace}/{action}

示例:

POST /v1/risk/evaluate-payment
{
    "code": "h43j5k6iop"
}

响应:

200 OK
{
    "status": "VALID"
}

搜索

在使用Resource Collections的时候,最好是使用查询参数来进行集合内容的过滤。不过有时候我们会需要一些更为复杂的查询语法,单纯的查询参数的方式会导致使用的问题或者受限于查询参数的长度。这时候,应该选择使用POST请求来指定查询参数。

Paging

如果响应体比较大的情况下应当使用分页方式,需要注意的是,Consumer应该在每次子请求的时候都使用POST方式。这样也就意味着,在POST请求体中需要维护一些查询参数。分页查询参数同样可以参考Resource Collections这一部分。同样可以适用于提供 next, previous, first, last 来进行其他页的快速读取。

POST /{version}/{namespace}/{search-resource}

示例:

POST /v1/factory/widgets-search
    {
        "created_before":"1975-05-13",
        "status": "ACTIVE",
        "vendor": "Parts Inc."
    }

响应:

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。

GET /{version}/{namespace}/{read-only-resource}

示例:

GET /v1/location/geocode?address=77+N.+Washington+Street%2C+Boston%2C+MA%2C+02114
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值