RESTful服务最佳实践——(九)

服务版本管理

通过内容协商支持版本管理

以往,是通过URI本身的版本号来完成版本管理的,客户直接在他们请求的URI中表明他们想要的资源的版本号。事实上,许多“大人物”如Twitter、Yammer、Facebook、Google等经常在他们的URI里使用版本号。甚至API管理工具(如WSO2)在暴露的URL里要求版本号。

面向REST原则,版本管理技术飞跃发展。因为它没有拥抱HTTP规范中内置的头部系统,也不支持仅当一个新的资源或概念被引入时才应该添加新URI的观点–即版本不是变化的表现形式。另一个反对理由是资源URI是不会随时间改变的,资源就是资源。

URI应该能简单地识别资源–而不是它的“形状”(状态)。另一个概念必须被用于指定响应的格式(表征)。“其他概念”是一对HTTP头:Accept 和 Content-Type。Accept头允许客户端指定他们希望或能支持的响应的(一种或多种)媒体类型。Content-Type头被客户端和服务器分别用于表明请求或响应体的数据体。

例如,在一个JSON格式数据中搜索用户数据:

请求:

GET http://api.example.com/users/12345
Accept: application/json; version=1

响应:

HTTP/1.1 200 OK
Content-Type: application/json; version=1

{“id”:”12345”, “name”:”Joe DiMaggio”}

现在,在JSON格式的相同资源数据中搜索版本2:

请求:

GET http :// api . example . com / users /12345
Accept: application/json; version=2

响应:

HTTP/1.1 200 OK
Content-Type: application/json; version=2

{“id”:”12345”, “firstName”:”Joe”, “lastName”:”DiMaggio”}

Accept头被用来表示所期望响应的格式(以及此例中的版本)时,注意URI是如何做到在两个版本中识别资源是一致的。或者,如果客户端需要一个XML格式的响应,Accept头将被设置为“application/xml”,如果需要的话可以带有一个指定的版本。

由于Accept头可以被设置为允许多种媒体类型,在响应请求时,服务器将把响应的Content-Type头设为最匹配客户端请求内容的类型。更多信息请查看http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.Html

例如:
请求:

GET http://api.example.com/users/12345
Accept: application/json; version=1, application/xml; version=1

上述请求中,假设服务器支持JSON 和XML格式的一种或两种请求类型,由服务器偏好哪种类型来决定。但无论服务器选择哪一种,格式的选择都将被设定在响应中的Content-Type头部。

例如,如果服务器的响应偏向于application/xml,则将是:
响应:

HTTP/1.1 200 OK
Content-Type: application/xml; version=1

<user>
    <id>12345</id>
    <name>Joe DiMaggio</name>
</user>

为了说明Content-Type在发送数据给服务器时的用处,这里给出一个用JSON格式创建新用户的例子:

请求:

POST http://api.example.com/users
Content-Type: application/json;version=1

{“name”:”Marco Polo”}

或者,如果调用版本2的接口:

请求:

POST http://api.example.com/users
Content-Type: application/json;version=2

{“firstName”:”Marco”, “lastName”:”Polo”}

当没有指定版本时,返回什么版本?

在每一个请求中,提供版本是非必须的。由于HTTP内容协商遵循内容类型的“最佳匹配”方法,所以你的API也应该这样。根据这种“最佳匹配”概念,当用户没有指定版本时,API应当返回最早的支持版本的表征。

例如,在一个JSON格式数据中搜索用户数据:

请求:

GET http://api.example.com/users/12345 
Accept: application/json

响应:

HTTP/1.1 200 OK 
Content-Type: application/json; version=1

{“id”:”12345”, “name”:”Joe DiMaggio”}

类似地,当将数据以不含版本形式发布到支持多个版本的终端时,上述相同的规则可适用—按预期,最低/最早支持的版本包含在body内。为了说明,这里给出一个在多版本终端上用JSON格式创建新用户的例子(预期版本1):

请求:

POST http://api.example.com/users 
Content-Type: application/json

{“name”:”Marco Polo”}

响应:

HTTP/1.1 201 OK 
Content-Type: application/json; version=1 
Location: http://api.example.com/users/12345

{“id”:”12345”, “name”:”Marco Polo”}

请求不支持的版本

当请求一个不支持的版本号(包含在API消亡的生命周期中消失的资源版本)时,API应当返回406(不被接受)HTTP状态码的错误响应。此外,API应当返回一个带有Content-Type: application/json的响应体,包含一个该终端支持的内容类型的JSON数组。

请求:

For example: 
GET http://api.example.com/users/12345 
Content-Type: application/json; version=999

响应:

HTTP/1.1 406 NOT ACCEPTABLE 
Content-Type: application/json

[“application/json; version=1”, “application/json; version=2”, “application/xml; version=1”, “application/xml; version=2”]

我应该在什么时候创建一个新版本?

API开发中有很多方法来打破约定,负面地影响客户。如果你不确定你的改变的后果,最好保险起见且考虑版本控制。当你试图决定一个新版本是否合适或者修改现有的表征是否就足够并可接受时,有几个因素要考虑。

会打破规则的改变

  • 改变属性名(例如:将“name”变为“firstName”)
  • 删除属性
  • 改变属性的数据类型(将numeric变为string, boolean变为bit/numeric,string 变为 datetime等等)
  • 验证规则改变
  • 在Atom式链接中,修改“rel”的值
  • 在现有的工作流中引入必要资源
  • 资源概念/意图改变;概念/意图或资源状态的意义不同于它原始的意义。例子:
    • 带有内容类型text/html的资源,曾意味着表征是连接到所有支持的媒体类型的一个“链接”集,而新的text/html表征意味着用户输入的“web浏览器形式”。
    • 一个在资源“…/users/{id}/exams/{id}”中带有“endTime”参数的接口,曾代表学生在那个时间提交试卷,而新的含义是考试的预定结束时间。
  • 意欲推翻现有的资源,添加来自现有资源的新字段。结合两种资源为一体,推翻两种原始资源。
    • 有“…/users/{id}/dropboxBaskets/{id}/messages/{id}”和“…/users/{id}/dropboxBaskets/{id}/messages/{id}/readStatus”两种资源。新要求是把readStatus资源的属性放到个人信息资源中,并推翻readStatus资源。这将导致个人信息资源中的readStatus资源链接被移除。

虽然这个列表不是全面的,但它能提醒你将对你的客户端造成灾难的变化类型,并提醒你需要一个新资源或新版本。

非破坏性的改变

  • 被添加到JSON响应中的新属性
  • 连接到其他资源的新的/额外的“链接”
  • content-type支持的新格式
  • content-language支持的新格式
  • 由于API的创建者和消费者都应该处理不同的casing,因此casing是无关破坏的

版本控制应在什么级别出现?

建议在个人资源级别进行版本控制。如修改工作流,API的一些变化也许会需要跨多个资源的版本控制来防止破坏客户端。

利用Content-Location来加强响应

可选。见RDF 章节。

带有Content-Type的链接

Atom风格的链接支持“type”属性。提供足够的信息以便客户端可以构造对特定版本和内容类型的必要调用。

找出支持的版本

我应该同时支持多少个版本?

由于维护许多版本变得繁琐、复杂、容易出错,且代价高,所以对于任何给定的资源,你应该支持不超过2个版本。

弃用的

弃用这一术语的目的是用来说明资源对API仍然可用,但将变得不可用,且将来不再存在。注意:弃用的时间长度将由弃用策略决定–并没有定义。

我怎么告知客户端被弃用的资源?

许多客户端可能将要或正在使用新版本被引入后就将被弃用的资源,这样,他们需要方法来发现和监控他们的应用程序所用的弃用资源。当请求一个弃用资源时,API应该以一个正常响应,带有布尔类型的自定义头“Deprecated”。以下用一个例子说明。

请求:

GET http://api.example.com/users/12345 
Accept: application/json 
Content-Type: application/json; version=1

响应:

HTTP/1.1 200 OK 
Content-Type: application/json; version=1 
Deprecated: true 
{“id”:”12345”, “name”:”Joe DiMaggio”}

原文如下


Service Versioning

Straight-up, versioning is hard, arduous, difficult, fraught with heartache, even pain and extreme sadness–let’s just say it adds a lot of complexity to an API and possibly to the clients that access it. Consequently, be deliberate in your API design and make efforts to not need versioned representations.

Favor not versioning, instead of using versioning as a crutch for poor API design. You’ll hate yourself in the morning if you need to version your APIs at all, let alone frequently. Lean on the idea that with the advent of JSON usage for representations, clients can be tolerant to new properties appearing in a response without breaking. But even that is laden with danger in certain cases, such as changing the meaning of an existing property with either contents or validation rules.

Inevitably there will come a time when an API requires a change to its returned or expected representation that will cause consumers to break and that breaking change must be avoided. Versioning your API is the way to avoid breaking your clients and consumers.

Support Versioning via Content Negotiation

Historically versioning was accomplished via a version number in the URI itself, with clients indicating which version of a resource they desired directly in the URI they requested. In fact, many of the “big boys” such as Twitter, Yammer, Facebook, Google, etc. frequently utilize version numbers in their URIs. Even API management tools such as WSO2 have required version numbers in the exposed URLs.

This technique flies in the face of the REST constraints as it doesn’t embrace the built-in header system of the HTTP specification, nor does it support the idea that a new URI should be added only when a new resource or concept is introduced–not representation changes. Another argument against it is that resource URIs aren’t meant to change over time. A resource is a resource.

The URI should be simply to identify the resource–not its ‘shape’. Another concept must be used to specify the format of the response (representation). That “other concept” is a pair of HTTP headers: Accept and Content-Type. The Accept header allows clients to specify the media type (or types) of the response they desire or can support. The Content-Type header is used by both clients and servers to indicate the format of the request or response body, respectively.

For example, to retrieve a user in JSON format:

# Request
GET http://api.example.com/users/12345
Accept: application/json; version=1

# Response
HTTP/1.1 200 OK
Content-Type: application/json; version=1

{“id”:”12345”, “name”:”Joe DiMaggio”}

Now, to retrieve version 2 of that same resource in JSON format:

# Request
GET http://api.example.com/users/12345
Accept: application/json; version=2

# Response
HTTP/1.1 200 OK
Content-Type: application/json; version=2

{“id”:”12345”, “firstName”:”Joe”, “lastName”:”DiMaggio”}

Notice how the URI is the same for both versions as it identifies the resource, with the Accept header being used to indicate the format (and version in this case) of the desired response. Alternatively, if the client desired an XML formatted response, the Accept header would be set to ‘application/xml’ instead, with a version specified, if needed.

Since the Accept header can be set to allow multiple media types, in responding to the request, a server will set the Content-Type header on the response to the type that best matches what was requested by the client. Please see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more information.

For example:

# Request
GET http :// api . example . com / users/12345
Accept: application/json; version=1, application/xml; version=1

The above request, assuming the server supports one or both of the requested types, will either be in JSON or XML format, depending on which the server favors. But whichever the server chooses, will be set on the Content-Type header in the response.

For example, the response from the server if it favors application/xml would be:

# Response
HTTP/1.1 200 OK
Content-Type: application/xml; version=1

<user>
    <id>12345</id>
    <name>Joe DiMaggio</name>
</user>

To illustrate the use of Content-Type when sending data to the server, here is an example of creating a new user using JSON format:

# Request
POST http://api.example.com/users
Content-Type: application/json; version=1

{“name”:”Marco Polo”}

Or, if version 2 was in play:

# Request
POST http :// api . example . com /users
Content-Type: application/json; version=2

{“firstName”:Marco”, “lastName”:Polo”}

What version is returned when no version is specified?

Supplying a version on each request is optional. As HTTP content-negotiation follows a “best match” approach with content types, so should your APIs. Using this “best match” concept, when the consumer does not specify a version, the API should return the oldest supported version of the representation.

For example, to retrieve a user in JSON format:

# Request
GET http://api.example.com/users/12345
Accept: application/json

# Response
HTTP/1.1 200 OK
Content-Type: application/json; version=1

{“id”:”12345”, “name”:”Joe DiMaggio”}

Similarly, when POSTing data to an endpoint that supports multiple versions without a version, the same rules as above apply–the lowest/earliest supported version is expected in the body. To illustrate, here is an example of creating a new user on a multi-version endpoint using JSON format (it expects version 1):

# Request
POST http :// api . example . com /users
Content-Type: application/json

{“name”:”Marco Polo”}

# Response
HTTP/1.1 201 OK
Content-Type: application/json; version=1
Location: http://api.example.com/users/12345

{“id”:”12345”, “name”:”Marco Polo”}

Unsupported Versions Requested

When an unsupported version number is requested, including a resource version that has gone through the API deprecation lifecycle, the API should return an error response with 406 (Not Acceptable) HTTP status code. In addition, the API should return a response body with Content-Type: application/json that contains a JSON array of supported content types for that endpoint.

# Request
For example:
GET http :// api . example . com /users/12345
Content-Type: application/json; version=999

# Response
HTTP/1.1 406 NOT ACCEPTABLE
Content-Type: application/json

[“application/json; version=1”, “application/json; version=2”, “application/xml; version=1”, “application/xml; version=2”]

When Should I Create a New Version?

In API development there are many ways to break a contract and negatively impact your clients. If you are uncertain of the consequences of your change it is better to play it safe and consider versioning. There are several factors to consider when you are trying to decide if a new version is appropriate or if a modification of an existing representation is sufficient and acceptable.

Changes that will break contracts

  • Changing a property name (ie. “name” to “firstName”)
  • Removal of property
  • Changing property data type (numeric to string, boolean to bit/numeric, string to datetime, etc.)
  • Validation rule change
  • In Atom style links, modifying the “rel” value.
  • A required resource is being introduced into an existing workflow
  • Resource concept/intent change; the concept/intent or the meaning of the resource’s state has a different meaning from it’s original. Examples:
    • A resource with the content type text/html once meant that the representation would be a collection of “links” to all supported media types, new text/html representation means “web browser form” for user input.
    • An API populating an “endTime” on the resource “…/users/{id}/exams/{id}” once meant the student submitted the exam at that time, the new meaning is that it will be the scheduled end time of the exam.
  • Adding new fields that came from an existing resource with the intent to deprecate the existing resource. Combining two resources into one and deprecating the two original resources.
    • There are two resources, “…/users/{id}/dropboxBaskets/{id}/messages/{id}” and “…/users/{id}/dropboxBaskets/{id}/messages/{id}/readStatus”. The new requirement is to put the properties from the readStatus resource into the individual message resource and deprecate the readStatus resource. This will cause the removal of a link to the readStatus resource in the individual messages resource.

While this list is not full-inclusive, it gives you an idea of the types of changes that will cause havoc for your clients and require a new resource or a new version.

Changes considered non-breaking

  • New properties added to a JSON response.
  • New/additional “link” to other resources.
  • New content-type supported formats.
  • New content-language supported formats.
  • Casing is irrelevant as both the API producer and consumer should handle varied casing.

At What Level Should Versioning Occur?

It is recommended to version at the individual resource level. Some changes to an API such as modifying the workflow may require versioning across multiple resource to prevent breaking clients.

Use Content-Location to Enhance Responses

Optional. See RDF spec.

Atom-style links support a ‘type’ property. Provide enough information so that clients can construct necessary calls to specific version & content type.

Finding Out What Versions are Supported

How many versions should I support at once?

Since maintaining many versions becomes cumbersome, complex, error prone, and costly you should support no more than 2 versions for any given resource.

Deprecated

The term deprecated is intended to be used to communicate that a resource is still available by the API, but will become unavailable and no longer exist in the future. Note: The length of time in deprecation will be determined by the deprecation policy- not yet defined.

How do I inform clients about deprecated resources?

Many clients will be using resources that are to be deprecated after new versions are introduced and in doing so, they will need ways to discover and monitor their applications use of deprecated resources. When a deprecated resource is requested, the API should return a normal response with the Pearson custom Header “Deprecated” in a boolean format. Below is an example to illustrate.

# Request
GET http://api.example.com/users/12345
Accept: application/json
Content-Type: application/json; version=1

# Response
HTTP/1.1 200 OK
Content-Type: application/json; version=1
Deprecated: true

{“id”:”12345”, “name”:”Joe DiMaggio”}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值