版本控制REST API

Ram*_*yer 31 api rest

在阅读了很多关于REST版本的材料之后,我正在考虑对调用进行版本控制而不是API.例如:

http://api.mydomain.com/callfoo/v2.0/param1/param2/param3
http://api.mydomain.com/verifyfoo/v1.0/param1/param2
Run Code Online (Sandbox Code Playgroud)

而不是第一次拥有

http://api.mydomain.com/v1.0/callfoo/param1/param2
http://api.mydomain.com/v1.0/verifyfoo/param1/param2
Run Code Online (Sandbox Code Playgroud)

然后去

http://api.mydomain.com/v2.0/callfoo/param1/param2/param3
http://api.mydomain.com/v2.0/verifyfoo/param1/param2
Run Code Online (Sandbox Code Playgroud)

我看到的优点是:

  • 当呼叫改变时,我不必重写我的整个客户端 - 只有受改变的呼叫影响的部分.
  • 那些工作良好的客户端部分可以继续保持原样(我们投入了大量的测试时间来确保客户端和服务器端都稳定.)
  • 对于已更改的呼叫,我可以使用永久或非永久重定向.
  • 向后兼容性将是轻而易举的,因为我可以保留较旧的呼叫版本.

我错过了什么吗?请指教.

Joe*_*dge 58

需要HTTP标头.

Version: 1

版本头临时注册在RFC 4229,有避免一些正当的理由使用X-前缀或用途专有的URI.yfeldblum/sf/answers/142006511/上提出了一个更典型的标题:

X-API-Version: 1

在任何一种情况下,如果标头丢失或与服务器可以提供的不匹配,请发送412 Precondition Failed响应代码以及失败原因.这要求客户端每次指定它们支持的版本,但在客户端和服务器之间强制执行一致的响应.(可选地支持?version=查询参数将为客户提供额外的灵活性.)

这种方法简单,易于实现且符合标准.

备择方案

我知道一些非常聪明,善意的人建议使用URL版本控制和内容协商.在某些情况下以及通常提出的形式都存在重大问题.

网址版本控制

如果您控制所有服务器和客户端,则端点/服务URL版本控制可用.否则,您将需要处理回退到较旧服务器的较新客户端,您最终会使用自定义HTTP标头,因为部署在您控制之外的异构服务器上的服务器软件的系统管理员可以做各种事情搞砸了如果你使用像302 Moved Temporarily这样的东西,你认为很容易解析的URL .

内容谈判

Accept如果您非常关注遵循HTTP标准但又想忽略HTTP/1.1标准文档实际所说的内容,则通过标头进行内容协商会起作用.您倾向于看到的建议的MIME类型是某种形式application/vnd.example.v1+json.有一些问题:

  1. 当然,有些情况下供应商扩展实际上是合适的,但客户端和服务器之间略有不同的通信行为并不真正符合新"媒体类型"的定义.此外,RFC 2616(HTTP/1.1)读取" 媒体类型值已在Internet分配号码机构注册.媒体类型注册过程在RFC 1590中列出.不鼓励使用未注册的媒体类型." 我不希望为每个具有REST API的软件产品的每个版本都看到单独的媒体类型.
  2. 任何子类型范围(例如application/*)都没有意义.对于将结构化数据返回给客户端进行处理和格式化的REST API,有什么好处可以接受*/*
  3. Accept头需要一些努力正确解析.应该遵循隐含和明确的优先级,以最小化实际正确进行内容协商所需的来回.如果您担心正确实施此标准,这一点很重要.
  4. RFC 2616(HTTP/1.1)描述了任何不包含Accept标头的客户端的行为:"如果不存在Accept标头字段,则假定客户端接受所有媒体类型." 因此,对于您不自己编写的客户端(控制权最少的地方),最正确的做法是使用服务器知道的最新,最容易破解的旧版本来响应请求关于.换句话说,你可能根本没有实现版本控制,那些客户端仍然会以完全相同的方式破解.

2014年编辑:

我已经阅读了很多其他答案和每个人的深思熟虑的评论; 我希望通过几年的反馈,我可以改进这一点:

  1. 不要使用'X-'前缀.我认为Accept-Version在2014年可能更有意义,并且Version在评论中提出的重新使用语义存在一些有效的担忧.与定义的标题重叠,Content-Version以及URI的相对不透明性是肯定的,我试着要小心将两者混淆内容协商的变体,Version标题实际上是这样.https://example.com/api/212315c2-668d-11e4-80c7-20c9d048772b无论URL 是否包含数据或文档,URL的第三个"版本"都与"第二个"完全不同.
  2. 关于我上面提到的关于URL版本控制(https://example.com/v1/users例如端点)的说法,反过来可能更有道理:如果你控制所有服务器和客户端,URL/URI版本控制可能就是你想要的.对于可以发布单个服务URL的大规模服务,我会为每个版本使用不同的端点,就像大多数一样.我的特殊考虑很大程度上受到以下事实的影响:上面描述的实现最常被许多不同的组织部署在许多不同的服务器上,并且可能最重要的是,在我无法控制的服务器上.我一直希望有一个规范的服务URL,如果一个网站仍在运行的API v3的版本,我绝对不希望的请求https://example.com/v4/回来与他们的网络服务器的404未找到页面(或者更糟,200 OK那将他们的主页作为500k的HTML通过蜂窝数据返回到iPhone应用程序.)
  3. 如果你想要非常简单的/客户端/实现(以及更广泛的采用),很难说在HTTP请求中要求自定义标头对于客户端作者来说就像GET一个vanilla URL 一样简单.(尽管身份验证通常要求您的令牌或凭证在标题中传递.无论如何,使用VersionAccept-Version作为秘密握手以及实际的秘密握手非常适合.)
  4. 使用Accept标头的内容协商有利于为相同的内容获取不同的MIME类型(例如,XML与JSON对比Adobe PDF),但没有为这些事物的版本定义(Dublin Core 1.1与JSONP对比PDF/A) .如果您希望支持Accept标题,因为尊重行业标准很重要,那么您不希望虚构的MIME类型干扰您可能需要在请求中使用的媒体类型协商.保证定制的API版本标头不会干扰频繁使用的经常引用的标题Accept,而将它们混合到相同的用法中只会让服务器和客户端混淆.也就是说,出于多种原因,将每个2013年的RFC6906命名的命名空间命名为单独的标题.这非常聪明,我认为人们应该认真考虑这种方法.
  5. 为每个请求添加标头是在无状态协议中工作的一个特殊缺点.
  6. 恶意代理服务器几乎可以做任何事情来破坏HTTP请求和响应.它们不应该,虽然我在这个上下文中没有谈论Cache-ControlVary标头,但是所有服务创建者都应该仔细考虑如何在许多不同的环境中使用它们的内容.


Che*_*eso 16

这是一个意见问题; 这是我的,以及意见背后的动机.

  1. 在URL中包含该版本.
    对于那些说,它属于HTTP标题的人,我说:也许吧.但是根据该领域的早期领导者的说法,输入URL是可接受的方式.(谷歌,雅虎,推特等).这是开发人员期望和做开发人员所期望的,换句话说,按照最不惊讶的原则行事,可能是一个好主意.它绝对不会让"客户更难升级".如果URL的更改以某种方式对消费应用程序的开发人员构成障碍,如此处的不同答案所示,则需要触发开发人员.

  2. 跳过次要版本
    有很多整数.你不会跑出去的.那里你不需要小数.无论如何,从API到1.0的任何更改都不应该破坏现有客户端.所以只需使用自然数.如果你想使用分离来暗示更大的变化,你可以从v100开始并做v200等等,但即使在那里我认为YAGNI并且它太过分了.

  3. 将最左边的版本放在URI中
    可能会在模型中有多个资源.它们都需要同步版本化.你不能让人们使用资源X的v1和资源Y的v2.它会破坏某些东西.如果你试图支持它会在你添加版本时创建一个维护噩梦,并且无论如何都没有为开发者添加任何价值.那么,资源的类型http://api.mydomain.com/v1/Resource/12345在哪里Resource,12345并被资源id替换.

你没问,但......

  1. 从您的URL路径中省略动词
    REST是面向资源的.你的URL路径中有"CallFoo"之类的东西,它看起来像一个动词,而且与名词不同.这是错的.使用力量,卢克. 使用属于REST的动词:GET PUT POST DELETE等等.如果您想获得资源验证,那么就做GET http://domain/v1/Foo/12345/verification.如果你想更新它,做POST /v1/Foo/12345.

  2. 将可选参数作为查询参数或有效负载放置可选参数
    不应位于U​​RL路径中(在第一个问号之前),除非您建议这些可选参数构成一个独立的资源.所以,POST /v1/Foo/12345?action=partialUpdate&param1=123&param2=abc.