API版本控制的最佳实践?

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

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

607856 次浏览

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

由于应用程序的演变,在较小程度上,它的API是生活的事实,它甚至类似于像编程语言这样看似复杂的产品的演变,URI设计应该有更少的自然约束应该随着时间的推移而保留。应用程序和API的生命周期越长,对应用程序和API用户的承诺就越大。

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

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

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

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

当然,可以在base URI中嵌入API版本但是仅用于合理和受限的用途,例如调试API客户端适用于新的API版本。这种版本化的API应该是有时间限制的,并且仅对有限的API用户组可用(例如在封闭测试期间)。否则,你会把自己提交到不应该提交的地方。

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

从API用户POV来看,当特定API版本如此明显但仅限于有限的时间(即在开发期间)时,使用和绑定到特定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/1234http://shonzilla/api/v3.0/customers/1234http://shonzilla/api/v3/customers/1234

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

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

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

http://shonzilla/api/customers/1234

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

  • 301永久移动表示具有请求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.1Accept: application/xml
<====HTTP/1.1 200 OKContent-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.1Accept: application/vnd.company.myapp.customer-v3+xml
<===HTTP/1.1 200 OKContent-Type: application/vnd.company.myapp-v3+xml<customer><name>Neil Armstrong</name></customer>

此外,假设客户端认为XML过于冗长,现在他们想要JSON。在其他示例中,您必须为同一客户拥有一个新URL,因此您最终会得到:

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

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

(GOOD WAY TO DO IT)===>GET /customer/123 HTTP/1.1Accept: application/vnd.company.myapp.customer-v3+json
<===HTTP/1.1 200 OKContent-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,而shito对象是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类型)的一个大问题是对超文本标记语言和JavaScript中的Accept和Content-Type标头的写入支持很差。

例如,IMO不可能在HTML5表单中使用以下标头发布以创建资源:

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

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

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

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

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

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

  2. 在接受:标题中,所以版本是文件类型。就像'mp3'vs'mp4'。这也可以,尽管在我看来它的效果不如…

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

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

  1. 如果您喜欢“清扫”新API,或者对于您想要这种方法的主要版本更改。
  2. 如果您希望客户端在执行PUT/POST之前知道它是否有效。
  3. 如果客户端必须执行PUT/POST以确定它是否有效,则可以。

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

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

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

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