Versioning a REST API – 对REST API进行版本管理

最后修改: 2013年 7月 30日

中文/混合/英文(键盘快捷键:t)

1. The Problem

1.问题

Evolving a REST API is a difficult problem – one for which many options are available. This article discusses some of these options.

改进REST API是一个困难的问题–在这个问题上有许多选择。本文讨论了其中的一些选择。

2. What Is in the Contract?

2.合同里有什么?

Before anything else, we need to answer one simple question: What is the Contract between the API and the Client?

在做其他事情之前,我们需要先回答一个简单的问题。API和客户端之间的合同是什么?

2.1. URIs part of the Contract?

Let’s first consider the URI structure of the REST API – is that part of the contract? Should clients bookmark, hardcode and generally rely on URIs of the API?

让我们首先考虑REST API的URI结构–那是合同的一部分吗?客户是否应该对API的URI进行书签、硬编码和普遍依赖?

If this the case, then the interaction of the Client with the REST Service would no longer be driven by the Service itself, but by what Roy Fielding calls out-of-band information:

如果是这样,那么客户与REST服务的交互将不再由服务本身驱动,而是由Roy Fielding呼叫 带外信息驱动。

A REST API should be entered with no prior knowledge beyond the initial URI (bookmark) and set of standardized media types that are appropriate for the intended audience…Failure here implies that out-of-band information is driving interaction instead of hypertext.

在进入REST API时,除了最初的URI(书签)和一套适合目标受众的标准化媒体类型外,不应事先了解……这里的失败意味着带外信息在推动互动,而不是超文本。

So clearly URIs are not part of the contract! The client should only know a single URI – the entry point to the API. All other URIs should be discovered while consuming the API.

因此,很明显URI不是合同的一部分!客户端应该只知道一个URI–API的入口点。所有其他的URI应该在消费API的时候被发现。

2.2. Media Types part of the Contract?

What about the Media Type information used for the representations of Resources – are these part of the contract between the Client and the Service?

用于表现资源的媒体类型信息如何?这些是客户和服务之间合同的一部分吗?

In order to successfully consume the API, the Client must have prior knowledge of these Media Types. In fact, the definition of these media types represents the entire contract.

为了成功地消费该API,客户必须事先了解这些媒体类型。事实上,这些媒体类型的定义代表了整个合同的内容。

Therefore, this is where the REST Service should focus the most:

因此,这是REST服务最应该关注的地方。

A REST API should spend almost all of its descriptive effort in defining the media type(s) used for representing resources and driving application state, or in defining extended relation names and/or hypertext-enabled mark-up for existing standard media types.

REST API应该把几乎所有的描述性工作都花在定义用于表示资源和驱动应用状态的媒体类型上,或者定义现有标准媒体类型的扩展关系名称和/或超文本标记。

So the Media Type definitions are part of the contract and should be prior knowledge for the client that consumes the API. This is where standardization comes in.

因此,媒体类型定义是合同的一部分,应该是消费API的客户的先验知识。这就是标准化的意义所在。

We now have a good idea of what the contract is, let’s move on to how to actually tackle the versioning problem.

我们现在对合同有了一个很好的概念,让我们继续讨论如何实际解决版本控制问题。

3. High Level Options

3.高水平的选择

Let’s now discuss the high level approaches to versioning the REST API:

现在我们来讨论一下REST API版本化的高级方法。

  • URI Versioning – version the URI space using version indicators
  • Media Type Versioning – version the Representation of the Resource

When we introduce the version in the URI space, the Representations of Resources are considered immutable. So when changes need to be introduced in the API, a new URI space needs to be created.

当我们在URI空间中引入版本时,资源的表述被认为是不可改变的。所以当需要在API中引入变化时,需要创建一个新的URI空间。

For example, say an API publishes the following resources – users and privileges:

例如,假设一个API发布了以下资源–用户和权限。

http://host/v1/users
http://host/v1/privileges

Now, let’s consider that a breaking change in the users API requires introducing a second version:

现在,让我们考虑一下,users API的一个突破性变化需要引入第二个版本。

http://host/v2/users
http://host/v2/privileges

When we version the Media Type and extend the language, we go through Content Negotiation based on this header. The REST API would make use of custom vendor MIME media types instead of generic media types such as application/json. We’re going to version these media types instead of the URIs.

当我们对媒体类型进行改版并扩展语言时,我们根据此标头进行内容协商。REST API将利用自定义的供应商的MIME媒体类型,而不是诸如application/json等通用媒体类型。我们要用这些媒体类型来代替URI的版本。

For example:

比如说。

===>
GET /users/3 HTTP/1.1
Accept: application/vnd.myname.v1+json
<===
HTTP/1.1 200 OK
Content-Type: application/vnd.myname.v1+json
{
    "user": {
        "name": "John Smith"
    }
}

We can check out this ‘Custom Media Types for Rest APIs’ article for further information and examples regarding this subject.

我们可以查看这篇‘Custom Media Types for Rest APIs’文章,以了解有关这一主题的进一步信息和示例。

What’s important to understand here is that the client makes no assumptions about the structure of the response beyond what’s defined in the media type.

这里需要理解的是,除了媒体类型中定义的内容外,客户端对响应的结构不做任何假设

This is why generic media types are not ideal. These do not provide enough semantic information and force the client to use require additional hints to process the actual representation of the resource.

这就是为什么通用媒体类型并不理想。这些没有提供足够的语义信息,迫使客户端使用需要额外的提示来处理资源的实际表示。

An exception to this is using some other way of uniquely identifying the semantics of the content – such as an XML schema.

这方面的一个例外是使用一些其他方式来唯一地识别内容的语义–如XML模式。

4. Advantages and Disadvantages

4.优点和缺点

Now that we have a clear concept of what is part of the Contract between the Client and the Service, as well as a high-level overview of the options to version the API, let’s discuss the advantages and disadvantages of each approach.

现在我们对客户和服务之间的合同的一部分有了清晰的概念,也对API的版本选择有了一个高层次的概述,让我们讨论一下每种方法的优点和缺点。

First, introducing version identifiers in the URI leads to a very large URI footprint. This is due to the fact that any breaking change in any of the published APIs will introduce a whole new tree of representations for the entire API. Over time, this becomes a burden to maintain as well as a problem for the client – which now has more options to choose from.

首先,在URI中引入版本标识符会导致非常大的URI足迹。这是因为任何已发布的API中的任何突破性变化都会为整个API引入一棵全新的表示方法。随着时间的推移,这将成为维护的负担以及客户的问题–客户现在有更多的选择可以选择。

Version identifiers in the URI ARE also severely inflexible. There is no way to simply evolve the API of a single Resource, or a small subset of the overall API.

URI中的版本标识符也严重缺乏灵活性。没有办法简单地发展单个资源的API,或整个API的一个小子集。

As we mentioned before, this is an all or nothing approach. If part of the API moves to the new version, then the entire API has to move along with it. This also makes upgrading clients from v1 to v2 a major undertaking – which leads to slower upgrades and much longer sunset periods for the old versions.

正如我们之前提到的,这是一个全有或全无的方法。如果API的一部分转移到新的版本,那么整个API也必须随之转移。这也使得将客户端从v1版升级到v2版成为一项重要的工作–这导致旧版本的升级速度减慢,日落期更长。

HTTP Caching is also a major concern when it comes to versioning.

HTTP缓存也是涉及到版本管理时的一个主要问题。

From the perspective of proxy caches in the middle, each approach has advantages and disadvantages. If the URI is versioned, then the cache will need to keep multiple copies of each Resource – one for every version of the API. This puts a load on the cache and decreases the cache hit rate since different clients will use different versions.

从中间的代理缓存的角度来看,每种方法都有优势和劣势。如果URI是有版本的,那么缓存就需要为每个资源保留多个副本–每个版本的API都有一个。这就给缓存带来了负担,并降低了缓存的命中率,因为不同的客户会使用不同的版本。

Also, some cache invalidation mechanisms will no longer work. If the media type is the one that is versioned, then both the Client and the Service need to support the Vary HTTP header to indicate that there are multiple versions being cached.

另外,一些缓存失效机制将不再起作用。如果媒体类型是有版本的,那么客户端和服务都需要支持Vary HTTP header以表明有多个版本被缓存。

From the perspective of client caching however, the solution that versions the media type involves slightly more work than the one where URIs contain the version identifier. This is because it’s simply easier to cache something when its key is an URL than a media type.

然而,从客户端缓存的角度来看,版本媒体类型的解决方案比URI包含版本标识符的解决方案涉及的工作略多。这是因为当一个东西的关键是一个URL而不是一个媒体类型的时候,缓存起来就更容易了。

Let’s end this section with defining some goals (straight out of API Evolution):

让我们以定义一些目标来结束本节(直接从API Evolution)。

  • keep compatible changes out of names
  • avoid new major versions
  • makes changes backwards-compatible
  • think about forwards-compatibility

5. Possible Changes to the API

5.对API的可能改变

Next, let’s consider the types of changes to the REST API – these are introduced here:

接下来,让我们考虑一下REST API的变化类型–这里将介绍这些变化。

  • representation format changes
  • resource changes

5.1. Adding to the Representation of a Resource

5.1.添加到资源的表述中

The format documentation of the media type should be designed with forward compatibility in mind. Specifically, a client should ignore information that it doesn’t understand (which JSON does better than XML).

媒体类型的格式文件在设计时应考虑到前向兼容性。具体来说,客户端应该忽略它不理解的信息(JSON比XML做得更好)。

Now, adding information in the Representation of a resource will not break existing clients if these are correctly implemented.

现在,在资源的表述中添加信息将不会破坏现有的客户端,如果这些客户端被正确实施的话。

To continue our earlier example, adding the amount in the representation of the user will not be a breaking change:

继续我们前面的例子,在user的表示中添加amount不会是一个破坏性的改变。

{
    "user": {
        "name": "John Smith", 
        "amount": "300"
    }
}

5.2. Removing or Changing an Existing Representation

5.2.删除或改变现有的代表权

Removing, renaming or generally restructuring information in the design of existing representations is a breaking change for clients. This is because they already understand and rely on the old format.

在现有表述的设计中删除、重命名或一般地重组信息,对客户来说是一个突破性的改变。这是因为他们已经理解并依赖旧的格式。

This is where Content Negotiation comes in. For such changes, we can add a new vendor MIME media type.

这就是内容协商的作用。对于这种变化,我们可以添加一个新的供应商MIME媒体类型。

Let’s continue with the previous example. Say we want to break the name of the user into firstname and lastname:

让我们继续前面的例子。假设我们想把用户分成

===>
GET /users/3 HTTP/1.1
Accept: application/vnd.myname.v2+json
<===
HTTP/1.1 200 OK
Content-Type: application/vnd.myname.v2+json
{
    "user": {
        "firstname": "John", 
        "lastname": "Smith", 
        "amount": "300"
    }
}

As such, this does represent an incompatible change for the Client – which will have to request the new Representation and understand the new semantics. However, the URI space will remain stable and will not be affected.

因此,这对客户来说是一个不兼容的变化–客户将不得不请求新的表示法并理解新的语义。然而,URI空间将保持稳定,不会受到影响。

5.3. Major Semantic Changes

5.3.主要的语义变化

These are changes in the meaning of the Resources, the relations between them or what the map to in the backend. This kind of changes may require a new media type, or they may require publishing a new, sibling Resource next to the old one and making use of linking to point to it.

这些是资源的含义、它们之间的关系或在后端映射到什么的变化。这种变化可能需要一个新的媒体类型,或者它们可能需要在旧的资源旁边发布一个新的、同级别的资源,并利用链接来指向它。

While this sounds like using version identifiers in the URI all over again, the important distinction is that the new Resource is published independently of any other Resources in the API and will not fork the entire API at the root.

虽然这听起来像是在URI中重新使用版本标识符,但重要的区别是,新资源是独立于API中的任何其他资源发布的,不会从根部分叉整个API。

The REST API should adhere to the HATEOAS constraint. According to this, most of the URIs should be DISCOVERED by Clients, not hardcoded. Changing such an URI should not be considered an incompatible change. The new URI can replace the old one and Clients will be able to re-discover the URI and still function.

REST API应遵守HATEOAS的约束。根据这一点,大多数URI应该由客户端发现,而不是硬编码。改变这样的URI不应该被认为是不兼容的改变。新的URI可以取代旧的URI,而客户端将能够重新发现URI,并且仍然能够发挥作用。

It’s worth noting however that, while using version identifiers in the URI is problematic for all of these reasons, it is not un-RESTful in any way.

然而,值得注意的是,虽然由于所有这些原因,在URI中使用版本标识符是有问题的,但它在任何方面都不是非RESTful

6. Conclusion

6.结论

This article tried to provide an overview of the very diverse and difficult problem of evolving a REST Service. We discussed the two common solutions, advantages and disadvantages of each one, and ways to reason about these approaches in the context of REST.

本文试图对进化REST服务这一非常多样化和困难的问题进行概述。我们讨论了两种常见的解决方案、每种解决方案的优势和劣势,以及在REST背景下推理这些方法的方式。

The article concludes by making the case for the second solution – versioning the media types while examining the possible changes to a RESTful API.

文章最后提出了第二个解决方案–转换媒体类型的理由,同时研究了RESTful API可能发生的变化。

The full implementation of this tutorial can be found in GitHub project.

本教程的完整实现可以在GitHub项目中找到

7. Further Reading

7.进一步阅读

Usually, these reading resources are linked throughout the article, but in these case, there are simply too many good ones:

通常,这些阅读资源在整个文章中都有链接,但在这种情况下,好的资源简直太多了。