web服务REST API版本控制有什么已知的方法或最佳实践吗?

我注意到AWS通过端点的URL进行版本控制。这是实现同一目标的唯一途径还是其他途径?如果有多种方法,每种方法的优点是什么?


这是一个好问题,也是一个棘手的问题。URI设计的主题同时也是RESTAPI中最突出的部分,因此是对该API用户的潜在长期承诺。

由于一个应用程序的演变,在较小程度上,它的API是一个事实,它甚至类似于一个看似复杂的产品(如编程语言)的演变,URI设计应该具有较少的自然约束,并且应该随着时间的推移而保留。应用程序和API的寿命越长,对应用程序和API用户的承诺就越大。

另一方面,生活的另一个事实是,很难预见通过API消耗的所有资源及其方面。幸运的是,没有必要设计整个API,直到天启才会使用。正确定义所有资源端点以及每个资源和资源实例的寻址方案就足够了。

随着时间的推移,您可能需要向每个特定资源添加新资源和新属性,但一旦资源寻址方案公开并因此成为最终方案,API用户访问特定资源所遵循的方法不应改变。

此方法适用于早期API版本中支持的HTTP动词语义(例如PUT应始终更新/替换)和HTTP状态代码(它们应继续工作,以便在没有人为干预的情况下工作的API客户端能够继续工作)。

此外,由于将API版本嵌入到URI中会破坏超媒体作为应用状态引擎的概念(Roy T.Fieldings博士论文中所述),因为资源地址/URI会随着时间而改变,我认为API版本不应该长期保存在资源URI中,这意味着API用户可以依赖的资源URI应该是永久链接。

当然,可以在基URI中嵌入API版本,但仅限于合理和受限的用途,如调试使用新API版本的API客户端。这种版本化的API应该是有时间限制的,并且只对有限的API用户组可用(比如在封闭测试期间)。否则,你就要在不该做的地方做出承诺。

关于维护过期的API版本的一些想法。通常用于实现web服务的所有编程平台/语言(Java、.NET、PHP、Perl、Rails等)都允许将web服务端点轻松绑定到基本URI。通过这种方式,可以很容易地收集和保持不同API版本之间的文件/类/方法的集合。

从API用户POV的角度来看,当特定的API版本很明显但仅在有限的时间内(即在开发期间)使用和绑定它也更容易。

从API维护者的POV来看,通过使用主要以文件为最小(源代码)版本控制单元的源代码控制系统,可以更容易地并行维护不同的API版本。

然而,由于API版本在URI中清晰可见,因此有一个警告:人们可能也会反对这种方法,因为API历史在URI设计中变得可见/不可见,因此随着时间的推移很容易发生变化,这违反了REST的指导原则。我同意!

解决这一合理异议的方法是在无版本API基URI下实现最新的API版本。在这种情况下,API客户端开发人员可以选择:

针对最新的API客户端进行开发(承诺维护应用程序,以保护其免受可能会破坏其糟糕设计的API客户端的最终API更改的影响)。绑定到API的特定版本(这一点很明显),但仅限于有限的时间

例如,如果API v3.0是最新的API版本,则以下两个应为别名(即,与所有API请求的行为相同):

http://shonzilla/api/customers/1234
http://shonzilla/api/v3.0/customers/1234
http://shonzilla/api/v3/customers/1234

此外,如果仍试图指向旧API的API客户端使用的API版本已过时或不再受支持,则应通知他们使用最新的先前API版本。因此,访问以下任何过时的URI:

http://shonzilla/api/v2.2/customers/1234
http://shonzilla/api/v2.0/customers/1234
http://shonzilla/api/v2/customers/1234
http://shonzilla/api/v1.1/customers/1234
http://shonzilla/api/v1/customers/1234

应返回指示重定向的30x HTTP状态代码中的任何一个,该代码与重定向到资源URI的适当版本的位置HTTP标头一起使用,该资源URI仍然是以下版本:

http://shonzilla/api/customers/1234

至少有两个重定向HTTP状态代码适用于API版本控制方案:

301永久移动,指示具有请求的URI的资源永久移动到另一个URI(该URI应该是不包含API版本信息的资源实例永久链接)。此状态代码可用于指示过时/不受支持的API版本,通知API客户端已将版本化的资源URI替换为资源永久链接。302找到,指示请求的资源暂时位于另一位置,而请求的URI可能仍受支持。当无版本URI暂时不可用时,该状态代码可能很有用,并且应该使用重定向地址重复请求(例如,指向嵌入APi版本的URI),并且我们希望告诉客户端继续使用它(例如,永久链接)。其他场景可以在HTTP 1.1规范的重定向3xx章节中找到


我们发现将版本放在URL中既实用又有用。它可以让你一眼就知道你在用什么。如公认的答案所示,我们将/foo别名为/foo/(最新版本),以便于使用、更短/更干净的URL等。

永远保持向后兼容性通常成本高昂和/或非常困难。我们倾向于提前通知弃用、此处建议的重定向、文档和其他机制。


URL不应包含版本。版本与您请求的资源的“想法”无关。您应该尝试将URL视为您想要的概念的路径,而不是希望如何返回项目。版本规定了对象的表示,而不是对象的概念。正如其他海报所说,您应该在请求头中指定格式(包括版本)。

如果您查看具有版本的URL的完整HTTP请求,则如下所示:

(BAD WAY TO DO IT):

http://company.com/api/v3.0/customer/123
====>
GET v3.0/customer/123 HTTP/1.1
Accept: application/xml

<====
HTTP/1.1 200 OK
Content-Type: application/xml
<customer version="3.0">
  <name>Neil Armstrong</name>
</customer>

标题包含一行,其中包含您所要求的表示(“Accept:application/xml”)。这就是版本应该去的地方。每个人似乎都掩盖了这样一个事实,即你可能想要不同格式的同一件东西,而客户应该能够要求它想要什么。在上面的示例中,客户机要求资源的任何XML表示,而不是它想要的真正表示。理论上,服务器可以返回与请求完全无关的东西,只要它是XML,并且必须对其进行解析才能意识到它是错误的。

更好的方法是:

(GOOD WAY TO DO IT)

http://company.com/api/customer/123
===>
GET /customer/123 HTTP/1.1
Accept: application/vnd.company.myapp.customer-v3+xml

<===
HTTP/1.1 200 OK
Content-Type: application/vnd.company.myapp-v3+xml
<customer>
  <name>Neil Armstrong</name>
</customer>

此外,假设客户认为XML过于冗长,现在他们希望使用JSON。在其他示例中,您必须为同一客户创建一个新的URL,因此您将得到:

(BAD)
http://company.com/api/JSONv3.0/customers/123
  or
http://company.com/api/v3.0/customers/123?format="JSON"

(或类似的东西)。事实上,每个HTTP请求都包含您要查找的格式:

(GOOD WAY TO DO IT)
===>
GET /customer/123 HTTP/1.1
Accept: application/vnd.company.myapp.customer-v3+json

<===
HTTP/1.1 200 OK
Content-Type: application/vnd.company.myapp-v3+json

{"customer":
  {"name":"Neil Armstrong"}
}

使用此方法,您在设计上有了更大的自由度,并且实际上遵循了REST的原始思想。您可以在不中断客户端的情况下更改版本,或者在API更改时逐步更改客户端。如果选择停止支持表示,则可以使用HTTP状态代码或自定义代码响应请求。客户端还可以验证响应的格式是否正确,并验证XML。

还有很多其他优点,我在博客上讨论了其中的一些:http://thereisnorightway.blogspot.com/2011/02/versioning-and-types-in-resthttp-api.html

最后一个例子说明了将版本放在URL中是多么糟糕。假设您希望在对象中包含一些信息,并且您已经对各种对象进行了版本控制(客户是v3.0,订单是v2.0,发货对象是v4.2)。以下是您必须在客户端中提供的讨厌的URL:

(Another reason why version in the URL sucks)
http://company.com/api/v3.0/customer/123/v2.0/orders/4321/

我同意资源表示的版本控制更好地遵循REST方法。。。但是,自定义MIME类型(或附加版本参数的MIME类型)的一个大问题是,对HTML和JavaScript中的Accept和Content-Type头的支持较差。

例如,为了创建资源,IMO不可能使用HTML5格式的以下标头进行POST:

Accept: application/vnd.company.myapp-v3+json
Content-Type: application/vnd.company.myapp-v3+json 

这是因为HTML5 enctype属性是一个枚举,因此除了通常的application/x-www-formurlconted、multipart/form-data和text/plain之外的任何属性都是无效的。

…我也不确定HTML4中的所有浏览器都支持它(它具有更宽松的encytpe属性,但对于MIME类型是否被转发,这将是浏览器实现的问题)

因此,我现在觉得最合适的版本转换方式是通过URI,但我承认这不是“正确”的方式。


有几个地方可以在REST API中进行版本控制:

如URI中所述。如果重定向等使用得当,这可能很容易处理,甚至在美学上也会令人愉悦。在Accepts:header中,因此版本在文件类型中。比如“mp3”和“mp4”。这也会起作用,尽管IMO的效果比。。。在资源本身中。许多文件格式的版本号都嵌入其中,通常在页眉中;这允许较新的软件通过理解文件类型的所有现有版本来“正常工作”,而如果指定了不受支持的(较新的)版本,则较旧的软件可以启动。在RESTAPI的上下文中,这意味着您的URI永远不需要更改,只需要您对所传递数据的特定版本的响应。

我可以看出使用这三种方法的原因:

如果您喜欢“清理”新的API,或者在需要这种方法的地方进行重大版本更改。如果您希望客户端在执行PUT/POST之前知道它是否可以工作。如果客户机必须执行PUT/POST以确定是否可以工作。


将您的版本放入URI中。API的一个版本并不总是支持来自另一个版本的类型,因此认为资源只是从一个版本迁移到另一个的说法是完全错误的。这与将格式从XML转换为JSON不同。类型可能不存在,或者它们可能在语义上发生了变化。

版本是资源地址的一部分。您正在从一个API路由到另一个API。在头中隐藏地址不是RESTful的。


REST API的版本控制类似于任何其他API的版本。小的改动可以在适当的地方完成,大的改动可能需要一个全新的API。对您来说,最简单的方法是每次都从头开始,这意味着将版本放在URL中最有意义。如果你想让客户端的生活更轻松,你可以尝试保持向后兼容性,这可以通过弃用(永久重定向)、多个版本的资源等来实现。这需要更多的努力,也更麻烦。但这也是REST在“酷URI不改变”中鼓励的。

最后,它就像任何其他API设计一样。权衡努力与客户便利。考虑为您的API采用语义版本控制,这可以为您的客户明确新版本的向后兼容程度。