API版本控制的最佳做法?[关闭]


877

Web服务REST API版本是否存在任何已知的操作方法或最佳做法?

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

Answers:


682

这是一个很好且棘手的问题。URI设计主题同时是REST API的最重要部分,因此,对于该API的用户可能是长期的承诺

由于应用程序的发展以及在较小程度上其API的存在是生活中的事实,并且它甚至与看似复杂的产品(如编程语言)的发展相似,因此URI设计应具有较少的自然约束,并且应予以保留随着时间的流逝。应用程序和API的寿命越长,对应用程序和API用户的承诺就越大。

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

随着时间的流逝,您可能需要向每个特定资源添加新资源和新属性,但是一旦资源寻址方案公开并最终确定,API用户访问特定资源所遵循的方法就不应更改。

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

此外,由于将API版本嵌入URI将通过使资源地址/ URI随时间变化而破坏作为应用程序状态引擎超媒体的概念(在Roy T. Fieldings博士论文中指出),因此我得出结论,API版本不应长时间保存在资源URI中,这意味着API用户可以依赖的资源URI应该是永久链接

当然,可以在基本URI中嵌入API版本,只能用于合理和受限的用途,例如调试与新API版本一起使用的API客户端。这种版本化的API应该是有时间限制的,并且仅对有限的一组API用户可用(例如在封闭Beta中)。否则,您将自己奉献到不应该的地方。

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

从API用户POV来看,只要很明显但仅在有限的时间内(即在开发过程中),就可以更轻松地使用和绑定到特定的API版本。

从API维护者的POV出发,通过使用主要以文件(源代码)版本控制的最小单位工作的源代码控制系统,可以更轻松地并行维护不同的API版本。

但是,在URI中清晰可见的API版本中,有一个警告:由于URI设计中的API历史变得可见/可见 ,因此很可能会随着时间的推移而发生变化,这与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版本。因此,访问像这样的任何过时的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状态代码中的任何一个,这些状态代码LocationHTTP标头结合使用,HTTP标头重定向到资源URI的适当版本,该版本仍为:

http:// shonzilla / api / customers / 1234

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

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

  • 302找到,指示请求的资源临时位于另一个位置,而请求的URI可能仍受支持。当无版本URI暂时不可用,并且应该使用重定向地址(例如,指向嵌入了APi版本的URI)重复请求并且我们要告诉客户端继续使用它时,此状态代码可能很有用。永久链接)。

  • 其他场景可以在HTTP 1.1规范的Redirection 3xx一章中找到


142
当基础实现发生更改时,在URL中使用版本号不应被视为不良做法。“当服务的接口以非向后兼容的方式更改时,实际上已经创建了一个全新的服务...从客户的角度来看,服务不只是接口和某些非功能特性。如果服务接口以非向后兼容的方式更改,则它不再代表原始服务的实例,而是一种全新的服务。” ibm.com/developerworks/webservices/library/ws-version
benvolioT 2010年

7
您是否对添加带有版本号的标头有任何想法,以便客户或开发人员可以检查它?
webclimber 2010年

11
另请参见使用Accept标头指示客户端期望的版本:blog.steveklabnik.com/2011/07/03/…–
Weston Ruter

52
对于最后一部分:我会说过时且不再受支持的API应该返回410 Gone,因为重定向可能表示新位置兼容,否则不兼容。如果该API只是已过时而仍然存在,Warning则可以使用Response上的HTTP标头。
迈克尔·斯托姆

22
您如何处理已经使用稳定网址(例如shonzilla / api / customers / 1234)的客户端,并且要升级到新版本?您如何强制他们将V2(旧的)添加到URL?
Dejell 2013年

273

该网址不应包含版本。该版本与您所请求资源的“想法”无关。您应该尝试将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>

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

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

10
在Accept标头中处理独立的数据合同版本和服务合同版本似乎很麻烦,就像在URL中一样。还有其他选择吗?另外,如果我有多个终结点(肥皂,休息),是否也应在“接受”中指出,并让服务器端的路由服务确定指向正确终结点的方向?或者是否可以在URL中进行编码?
ideafountain 2011年

117
我不同意这一点,至少在你最后的理由时才如此。这似乎是在说URI的不同部分具有不同的版本。但这不是API版本的重点。关键是要为整个资源拥有一个版本。如果更改版本,则是不同的API资源。这就是为什么看不到company.com/api/v3.0/customer/123/v2.0/orders/4321而是company.com/api/v3.0/customer/123/orders/4321的原因您没有对资源的任何给定部分进行版本控制,而是对资源进行了整体版本控制。
fool4jesus

90
在标头中使用版本号似乎更好。但是使用URL更加实用:错误更少,调试最好,开发人员容易看到,可以在其余测试客户端中轻松修改。
Daniel Cerecedo

7
我认为BAD / GOOD简化了这个问题。API代表“应用程序编程接口”,而版本控制接口似乎是一个好主意。API并不仅仅是服务于资源。需要分开的是,有些人在谈论接口,而另一些人在谈论资源。如果您在“网络”标签中仔细查看google maps api,您会发现它们在url中包含api版本号。例如:身份验证期间的maps.google.com/maps/api/jsv2。jsv2是api号。
汤姆·格鲁纳

6
@Gili:实际上,您不应再使用-x它,因为RFC6648已弃用它。
乔纳森·W

98

我们发现将版本放在URL中既实用又有用。一眼就能看出正在使用的内容。我们将/ foo作为/ foo /(最新版本)的别名,以易于使用,更短/更简洁的URL等,如公认的答案所示。

永远保持向后兼容性通常是成本过高和/或非常困难的。我们希望提前通知弃用,此处建议的重定向,文档以及其他机制。


5
可接受的答案可能是正确的,也是最纯正的。但是,对于API的开发人员和日常用户而言,这无疑是最易于使用和设置的。最务实的方法。正如其他Google和Amazon指出的那样,也使用这种方法。
穆罕默德·雷汉

46

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

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

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

这是因为HTML5 enctype属性是一个枚举,因此任何其他比平常application/x-www-formurlencodedmultipart/form-data并且text/plain是无效的。

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

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


14
假设在标头中定义了版本控制的路由,则可以说使用本机表单提交的HTML表单将始终使用API​​的最新版本,因为它们不会传递要遵循的特定版本。但是,XHR请求确实确实允许您更改接受并读取内容类型标头。因此,基本形式确实是唯一的问题。
凯尔·海斯

我不确定是否同意URI最合适,但是Content-Type不适用于表单这一事实确实非常重要。
2013年

2
@Kyle,我在某个地方看到一个博客说,如果您未在请求标头中指定特定版本,则最好返回第一个api版本而不是最新版本,以获得最佳兼容性。
2014年

现在我想起来,这对我来说真的很有意义。
凯尔·海斯

@KyleHayes不会忘记iframe,视频/嵌入式和其他“ src / href”类型的代码。
pllee

21

将您的版本放在URI中。一个版本的API并不总是支持另一个版本的类型,因此关于将资源仅从一个版本迁移到另一个版本的说法是完全错误的。这与将格式从XML转换为JSON不同。这些类型可能不存在,或者在语义上可能已更改。

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


13

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

  1. 如前所述,在URI中。如果正确使用重定向之类的方法,这可能很容易处理,甚至在审美上也令人愉悦。

  2. 在Accepts:标头中,因此版本位于文件类型中。就像“ mp3”和“ mp4”一样。这也将起作用,尽管IMO的效果不如...

  3. 在资源本身中。许多文件格式通常在标头中嵌入其版本号;这样可以通过了解文件类型的所有现有版本来使较新的软件“正常运行”,而如果指定了不受支持的(较新的)版本,则较旧的软件可以正常运行。在REST API的上下文中,这意味着您的URI不必更改,而只需您对所处理数据的特定版本的响应即可。

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

  1. 如果您想进行“彻底清除”新的API,或者希望在主要版本更改时使用这种方法。
  2. 如果您希望客户端在执行PUT / POST之前知道它是否将正常工作。
  3. 如果客户端必须执行其PUT / POST来确定其是否可以正常工作,则可以。

8

REST API的版本控制类似于任何其他API的版本控制。可以进行较小的更改,较大的更改可能需要全新的API。最简单的方法是每次都从头开始,这是将版本放入URL最有意义的时候。如果要使客户端的生活更轻松,则可以尝试保持向后兼容性,这可以使用弃用(永久重定向),多个版本的资源等来完成。这比较麻烦,需要更多的精力。但这也是REST在“酷URI不变”中所鼓励的。

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.