877

是否有任何已知的 Web 服务 REST API 版本控制方法或最佳实践?

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

4

7 回答 7

682

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

由于应用程序及其 API 的演变是不争的事实,甚至类似于编程语言等看似复杂的产品的演变,因此URI 设计应具有较少的自然约束应保留随着时间的推移。应用程序和 API 的生命周期越长,对应用程序和 API 用户的承诺就越大。

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

随着时间的推移,您可能需要为每个特定资源添加新资源和新属性,但是一旦资源寻址方案公开并因此成为最终方案,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 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 客户端使用最新的先前 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 状态代码,这些代码与 HTTP 标头一起使用,Location该标头重定向到资源 URI 的适当版本,该版本仍然是这个:

http://shonzilla/api/customers/1234

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

  • 301 Moved Permanent表示具有请求 URI 的资源被永久移动到另一个 URI(应该是不包含 API 版本信息的资源实例永久链接)。此状态码可用于指示过时/不受支持的 API 版本,通知 API 客户端版本化资源 URI 已被资源永久链接替换

  • 302 Found表示请求的资源暂时位于另一个位置,而请求的URI可能仍然支持。当无版本 URI 暂时不可用并且应该使用重定向地址重复请求(例如,指向嵌入了 APi 版本的 URI)并且我们想告诉客户端继续使用它(即永久链接)。

  • 其他场景参见HTTP 1.1 规范的 Redirection 3xx 章节

于 2008-12-29T20:24:36.380 回答
273

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>

标题包含包含您要求的表示的行(“接受:应用程序/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,shipto 对象是 v4.2)。这是您必须在客户端中提供的讨厌的 URL:

(Another reason why version in the URL sucks)
http://company.com/api/v3.0/customer/123/v2.0/orders/4321/
于 2011-07-19T16:08:50.557 回答
98

我们发现将版本放在 URL 中是实用且有用的。它可以让您一目了然地知道您正在使用什么。正如公认的答案所暗示的那样,我们将 /foo 别名为 /foo/(最新版本)以便于使用、更短/更清晰的 URL 等。

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

于 2011-01-04T20:57:12.390 回答
46

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

例如,为了创建资源,IMO 不可能在 HTML5 表单中使用以下标头进行 POST:

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

这是因为 HTML5enctype属性是一个枚举,因此除了通常的 , 之外的任何内容application/x-www-formurlencoded都是multipart/form-data无效text/plain的。

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

正因为如此,我现在觉得最合适的版本化方式是通过 URI,但我接受这不是“正确”的方式。

于 2011-10-13T10:51:30.647 回答
21

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

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

于 2012-06-05T15:09:52.800 回答
13

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

  1. 如前所述,在 URI 中。如果重定向等使用得当,这可能是易于处理的,甚至是美观的。

  2. 在 Accepts: 标头中,因此版本位于文件类型中。就像“mp3”与“mp4”。这也将起作用,尽管 IMO 它的工作原理不如...

  3. 在资源本身。许多文件格式都嵌入了它们的版本号,通常在标题中;这允许较新的软件通过了解文件类型的所有现有版本来“正常工作”,而如果指定了不受支持的(较新)版本,则较旧的软件可以退出。在 REST API 的上下文中,这意味着您的 URI 永远不必更改,只需您对所传递的特定数据版本的响应即可。

我可以看到使用所有三种方法的原因:

  1. 如果您喜欢“彻底清扫”新 API,或者您想要这种方法的主要版本更改。
  2. 如果您希望客户端在执行 PUT/POST 之前知道它是否会工作。
  3. 如果客户端必须执行其 PUT/POST 来确定它是否可以工作,是否可以。
于 2011-10-24T16:39:53.580 回答
8

对 REST API 进行版本控制类似于对任何其他 API 进行版本控制。可以进行小的更改,大的更改可能需要一个全新的 API。对您来说最简单的方法是每次都从头开始,此时将版本放在 URL 中最有意义。如果您想让客户端的生活更轻松,您可以尝试保持向后兼容性,您可以通过弃用(永久重定向)、多个版本的资源等来做到这一点。这更繁琐,需要更多的努力。但这也是 REST 在“Cool URIs don't change”中所鼓励的。

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

于 2014-03-06T07:16:52.813 回答