Best practices for API versioning?
对于Web服务RESTAPI版本控制,有什么已知的方法或最佳实践吗?
我注意到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应该是permalink。好的。
当然,可以在基URI中嵌入API版本,但只能用于合理和受限的用途,如调试和新API版本一起工作的API客户端。这种版本化的API应该是有时间限制的,并且只能用于有限的API用户组(如在封闭的beta期间)。否则,你就把自己放在不该放的地方。好的。
关于API版本维护的一些想法,它们都有到期日期。通常用于实现Web服务的所有编程平台/语言(Java、.NET、PHP、Perl、Rails等)允许Web服务端点(S)与基础URI的容易绑定。通过这种方式,可以很容易地收集和保持文件/类/方法的集合在不同的API版本之间分开。好的。
从api用户pov来看,当某个特定的api版本如此明显,但仅限于有限的时间(即开发期间)时,使用它并绑定到该版本也更容易。好的。
从API维护人员的POV来看,通过使用主要处理文件的源代码控制系统作为(源代码)版本控制的最小单元,可以更容易地并行维护不同的API版本。好的。
但是,对于在URI中清晰可见的API版本,有一个警告:人们也可能反对这种方法,因为在URI设计中,API历史变得可见/并行,因此随着时间的推移,很容易发生变化,这违背了REST的准则。我同意!好的。
解决这个合理的异议的方法是在无版本API基URI下实现最新的API版本。在这种情况下,API客户端开发人员可以选择:好的。
针对最新的API进行开发(致力于维护应用程序,以保护其不受可能破坏其糟糕的API客户端的最终API更改的影响)。好的。
绑定到特定版本的API(很明显),但时间有限好的。
例如,如果api v3.0是最新的api版本,则以下两个应该是别名(即行为与所有api请求相同):好的。
1 2 3 | http://shonzilla/api/customers/1234 http://shonzilla/api/v3.0/customers/1234 http://shonzilla/api/v3/customers/1234 |
此外,如果仍试图指向旧API的API版本已过时或不再受支持,则应通知仍尝试指向旧API的API客户端使用最新的早期API版本。所以访问任何过时的URI,比如:好的。
1 2 3 4 5 | 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状态代码中的任何一个,该重定向与
1 | http://shonzilla/api/customers/1234 |
至少有两个重定向HTTP状态代码适用于API版本控制方案:好的。
301已永久移动,表示具有请求的URI的资源将永久移动到另一个URI(该URI应该是不包含API版本信息的资源实例permalink)。此状态代码可用于指示过时/不受支持的API版本,通知API客户端版本化的资源URI已被资源permalink替换。好的。
302发现,指示请求的资源临时位于其他位置,而请求的URI可能仍然受支持。当无版本的URI暂时不可用,并且应使用重定向地址(例如,指向嵌入API版本的URI)重复请求,并且我们希望告诉客户端继续使用它(即permalinks)时,此状态代码可能很有用。好的。
其他场景可以在HTTP 1.1规范的重定向3xx章节中找到。好的。
好啊。
URL不应包含版本。该版本与您请求的资源的"想法"无关。您应该试着将URL看作是您想要的概念的路径,而不是您想要返回项目的方式。版本指示对象的表示,而不是对象的概念。正如其他海报所说,您应该在请求头中指定格式(包括版本)。
如果您查看具有版本的URL的完整HTTP请求,则如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 | (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,并且必须对其进行解析以认识到它是错误的。
更好的方法是:
1 2 3 4 5 6 7 8 9 10 11 12 13 | (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,这样您最终将得到:
1 2 3 4 | (BAD) http://company.com/api/JSONv3.0/customers/123 or http://company.com/api/v3.0/customers/123?format="JSON" |
(或类似的东西)。实际上,每个HTTP请求都包含您要查找的格式:
1 2 3 4 5 6 7 8 9 10 11 12 | (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"} } |
使用这种方法,您在设计上有了更多的自由,并且实际上坚持了休息的原始想法。您可以在不中断客户机的情况下更改版本,也可以在更改API时逐步更改客户机。如果选择停止支持表示,则可以使用HTTP状态代码或自定义代码响应请求。客户机还可以验证响应的格式是否正确,并验证XML。
还有很多其他的优点,我在我的博客上讨论其中的一些:http://thereinsorightway.blogspot.com/2011/02/versioning-and-types-in-resthttp-api.html
最后一个示例说明如何将版本放入URL中是错误的。假设您想要在对象内部获得一些信息,并且您已经对各种对象进行了版本控制(客户是3.0版,订单是2.0版,ShipTo对象是4.2版)。这是您必须在客户机中提供的恶意URL:
1 2 | (Another reason why version in the URL sucks) http://company.com/api/v3.0/customer/123/v2.0/orders/4321/ |
我们发现把这个版本放在URL中既实用又有用。它使您很容易一目了然地知道您在使用什么。我们使用alias/foo to/foo/(最新版本),以方便使用、缩短/清理URL等,正如公认的答案所建议的那样。
永远保持向后兼容性通常成本高昂和/或非常困难。我们更愿意提前通知取消预测、这里建议的重定向、文档和其他机制。
我同意对资源表示形式进行版本控制更好地遵循REST方法……但是,自定义mime类型(或附加版本参数的mime类型)的一个大问题是,对HTML和javascript中的write-to-accept和content-type头的支持较差。
例如,IMO不可能在HTML5表单中使用以下标题发布,以便创建资源:
1 2 | Accept: application/vnd.company.myapp-v3+json Content-Type: application/vnd.company.myapp-v3+json |
这是因为html5
…我也不确定HTML4中的所有浏览器都支持它(它有一个更宽松的encytpe属性,但对于mime类型是否被转发则是一个浏览器实现问题)
正因为如此,我现在觉得最合适的版本方法是通过URI,但我接受这不是"正确"的方法。
将您的版本放在URI中。一个API的一个版本并不总是支持来自另一个版本的类型,所以仅仅将资源从一个版本迁移到另一个版本的说法显然是错误的。这与从XML到JSON的转换格式不同。类型可能不存在,或者它们可能在语义上发生了变化。
版本是资源地址的一部分。您正在从一个API路由到另一个API。在标题中隐藏地址是不安全的。
在RESTAPI中,有几个地方可以进行版本控制:
如前所述,在URI中。如果能很好地使用重定向和类似的方法,这会很容易处理,甚至美观。
在accepts:header中,所以版本在filetype中。比如"MP3"和"MP4"。这也会有效,尽管我觉得它比…
在资源本身。许多文件格式中都嵌入了它们的版本号,通常是在头文件中;这允许较新的软件通过理解文件类型的所有现有版本来"仅仅工作",而较旧的软件可以在指定了不受支持的(较新的)版本时进行Punt。在RESTAPI的上下文中,这意味着您的URI永远都不需要更改,只需要您对所传递数据的特定版本的响应。
我可以看到使用这三种方法的原因:
对RESTAPI进行版本控制类似于对任何其他API进行版本控制。小的更改可以就地完成,大的更改可能需要一个全新的API。对您来说,最简单的方法是每次从头开始,也就是将版本放到URL中最有意义。如果你想让客户的生活更容易,你可以试着保持向后的兼容性,这可以通过拒绝(永久重定向)、多个版本中的资源等来实现。这更为复杂,需要付出更多的努力。但这也是REST在"酷的URI不变"中鼓励的。
最后,它就像其他API设计一样。权衡工作与客户的便利性。考虑为您的API采用语义版本控制,这使您的客户清楚地知道新版本向后兼容的程度。