[关闭]
@levinzhang 2021-02-07T08:03:21.000000Z 字数 4789 阅读 768

关闭旧API的正确方式

摘要

废弃API端点是一项非常具有挑战性的工作,我们需要考虑所有依赖该API的客户端,避免出现级联的故障。针对该问题,有两个新的HTTP头信息草案,本文详细阐述了该如何使用这两个新的头信息安全地关闭API。


本文最初发表于HTTP Toolkit网站,经原作者Tim Perry授权由InfoQ中文站翻译分享。

万物都会有终结,HTTP API也不能例外。不论你的API今天看上去多么伟大,迟早有一天你会想发布一个全新的版本,新的版本能够更好地解决相同的问题,在各方面可能都会有所改善,但是具有了新的参数,与旧版本也无法兼容,或者你只是想彻底关闭旧的API。总而言之,你现在的API不会永远存在。

但是,这并不是轻而易举就能完成的,因为你的API有客户端。如果你关闭端点、参数或整个API而没有做出恰当的警告的话,那他们肯定会非常不爽。

那么,该怎样安全地关闭API,让你的用户尽可能地感到轻松愉快呢?

在这方面,我们有正确的做事方式,包括两个新的头信息草案,它们正在被新的IETF “Building Blocks for HTTP APIs”工作组进行标准化,旨在形成一个精确的过程。我们了解一下。

制定计划

初始第一步:检查相关的API是否真的有客户端。

希望你能有某些API的度量指标,至少在某些地方存有日志。如果没有的话,那把它们添加上,如果你有这些东西的话,并且你能确定没有人再使用这个API了,那么恭喜你,你赢了。现在,你就可以把它关掉,删掉代码,不要再管这篇文章了,好好睡一觉去吧。

下一个问题,如果比较遗憾,你无法去睡觉的话,那就要问问自己,除了关闭这个API,还有没有其他的方案。你关闭的所有东西都有可能破坏别人的代码,并且会消耗他们的时间来修复这些问题。如果API能够继续运行的话,对客户端的生态系统和整个web的健康都是有好处的。

在很多场景下,旧的API可以在内部进行转换,透明地转化成对新API的调用,这样能够避免维护两个完全独立的版本。这是Stripe的API版本管理方式的一个基本组成部分,他们在所有发生变化的API中都包含了转换,以确保对不兼容的旧版本API的请求能够继续像以前那样运行,根据需要自动转换请求和响应从而能够使用较新的代码。

这样的转换并不总是可行的,而且如果永远这样做的话会带来明显的额外复杂性,但是如果你能够做到这一点的话,就能为用户提供非常有价值的稳定性,并且能够节省大量废弃旧版本或维护旧版本相关的工作。

但是,如果这个服务/端点/参数已经用到了生产环境,而且继续支持它是不现实的做法,那么它必须要被淘汰。

要实现这一点,我们就要有一个计划。我们首先要问自己三个关键的问题:

计划准备就绪之后,我们就该把它告诉人们了。

沟通

首先,要把这一决定告诉人们。

发邮件到邮件列表,在Twitter上发推文,如果有API规范的话,对其进行更新(比如,OpenAPI在operationsparameters上有一个deprecated字段),并在相关的在线文档上大声强调这一点。

你应该包含上文提到的所有信息:他们应该做些什么作为替代方案,你建议他们什么时候开始迁移以及他们必须要进行迁移的最后期限(如果已经确定期限的话)。

在将这些信息告诉给人们之后,接下来就该告诉计算机了,而这就是新的IETF头信息能够发挥作用的地方了。

Deprecation头信息

Deprecation头信息能够告诉客户端请求的资源现在依然像以前那样运行,但是这种方式已经不再推荐使用了。通过一个简单的HTTP头信息,我们就能声明这一点:

  1. Deprecation: true

另外,我们还可以提供一个日期。这个日期告诉用户他们何时该开始进行迁移。这个日期可以是一个过去的时间(这代表他们应该立即开始迁移),也可以是将来的时间(通常这意味着他们要迁移到的新环境还没有准备就绪)。如下所示:

  1. Deprecation: Thu, 21 Jan 2021 23:59:59 GMT

如果你要废弃整个端点或服务,那么你可以在每个响应中都带上这样的头信息。如果你想要废弃的是一个具体的特性,可能是一个参数、请求方法或者请求体中的某个特定字段的话,那么你应该在该特性被使用的时候才在响应中包含这个头信息。

为了给客户端更多的信息,我们还可以使用Link HTTP响应头信息链接至端点或人类易读的文档。在同一个Link头信息中,我们可以包含多个这样的链接,只需要使用逗号进行分割即可(后面我们会看到一个完整的例子)。该规范定义了四个与API废弃相关的链接:

Deprecation链接

我们可以为deprecation链接指向一个人类易于阅读的描述:

  1. Link: <https://developer.example.com/deprecation>; rel="deprecation"; type="text/html"

这是告诉用户发生了什么以及他们该怎么办的主要方式。你应该始终使用它。如果还没有完整的详情和最终的关闭日期,那么即使只是一个占位符,这也是很有帮助的。在这种情况下,不要忘记让用户订阅更新,这可以采用邮件列表、RSS或其他类似的方式来实现。

Latest-Version链接

如果你希望客户端转移至API相同端点的最新版本,那么可以使用该链接指向它,如下所示:

  1. Link: <https://api.example.com/v10/customers>; rel="latest-version"

Successor-Version链接

如果你的API有多个可用的版本,通常最好每次向前迁移一个版本,而不是直接从最老的、现已废弃的版本跳到最新的版本。为了帮助解决这个问题,我们链接至已废弃版本的下一个版本,而不是最新的版本,如下所示:

  1. Link: <https://api.example.com/v2/customers>; rel="successor-version"

Alternate链接

如果该API没有新的等价版本,用户最好迁移到一个完全不同的资源,它可能是一个很好的替代方案,那么我们使用alternate链接来指明这一点,如下所示:

  1. Link: <https://api.example.com/v2/users/123/clients>; rel="alternate"

Sunset头信息

如果你知道了API何时完全关闭的话,那么就应该添加一个Sunset头信息

Sunset头信息告诉客户端API何时会停止运行。这是一个强制的截止时间:API客户端必须要在这个日期之前进行迁移,我们承诺在这个时间之前不会破坏任何事情。

在这里,我们必须要提供一个时间,它应该是一个未来的时间。不过,如果它是一个过去的时间,这也是可以的:此时就相当于说“这个API会在任意时刻关闭,你需要立即停止使用它”。它如下所示:

  1. Sunset: Tue, 20 Jul 2021 23:59:59 GMT

这非常简单,它不仅可以用到API关闭的场景中:我们可以用它来标记将来URL迁移的HTTP重定向,或者表明特定URL有限的生命周期(适用于临时性的内容,或者适用于具有监管要求的特定资源,比如数据保留策略)。它所说明的就是“这个端点可能在该日期后不会再按照你的预期运行,请做好准备”。

Sunset链接

该规范也提供了一个Sunset链接的关系。按照设计,它会链接至关于关闭特定端点更加详细的信息(如果你有deprecation链接的话,它们可能会是同一个)或者关于服务的通用Sunset策略。如下所示:

  1. Link: <http://developer.example.com/our-sunset-policy>;rel="sunset";type="text/html"

在这里我们也要指出,通用的Sunset策略是非常有用的!Sunset策略会告诉客户端当我们关闭端点的时候(比如,一年后替代方案上线),用户该如何确保他们能够得知这一情况(邮件列表、状态页面、HTTP头信息等)以及他们通常应该做些什么(更新、检查文档、遵从Link头信息)。

如果马上就要废弃某个API的话,添加这样的链接作用其实不大,但是如果你能在一年前就将其发布出去的话,你的客户端可能已经为此做好了准备。除此之外,发布Sunset/Deprecation策略的最好时间就是现在了。如果你恰好正在以某种方式编写Deprecation文档的话,这么做是值得考虑的。

组合到一起

按照设计,这些组成部分能够很好地协作。例如,为了表明某个最近废弃的API,该API会在6个月内彻底关闭,我们要链接至文档并提供下一个版本的直接链接,那么我们应该在响应中包含如下的头信息链接:

  1. Deprecation: Thu, 21 Jan 2021 23:59:59 GMT
  2. Sunset: Tue, 20 Jul 2021 23:59:59 GMT
  3. Link: <https://api.example.com/v2/customers>; rel="successor-version",
  4. <https://developer.example.com/shutting-down-customers-v1>; rel="deprecation"

渐进式关闭

如果所有的这些都已经准备到位,并且sunset截止时间已过,那么我们就可以将API关闭了。

但是,这并不意味着你需要立即且彻底消灭该API。渐进式关闭能够有助于确保任何使用该API的所有客户端都有最后的机会在它彻底消失之前得到最后一次警告。GitHub在2018年移除一些加密支持的时候曾经这样做:首先禁用一个小时,然后启用它,最后在两周后彻底禁用了它。

这里还有另外一个技巧:安卓在2015年为已废弃的原生API增加了越来越多的延迟,在彻底关闭API之前,最终达到了16秒的等待。这些渐进式的关闭为那些错过截止日期的客户端提供了一些灵活性,并且能够帮助那些没有注意到废弃时间点的客户端,从而能在API彻底关闭之前处理一些问题。

谨慎行事

不管采用哪种方式,只要你尽了最大的努力去沟通关于API关闭的事情,那么现在就可以关闭端点/特性/整个服务,删除代码,然后去睡个好觉了。

像这样小心谨慎地进行废弃和关闭,可以让你的客户端尽可能清楚地知道他们该如何依赖你的API,何时需要采取行动,以及他们需要做什么。这种变更可能是一件大事儿,这些信息是很重要的!

这些新的草案头信息让我们不仅可以与人类沟通,还可以将这些信息暴露给自动化系统。随着这些头信息的普及,我很高兴地开始看到有更多的工具建立在它们之上。通用的HTTP客户端可以根据这些数据自动记录有用的警告日志,API生成器本身也可以根据API规范处理越来越多的问题,而HTTP调试器(如HTTP Toolkit)可以在截获的实时流量中为你突出显示废弃端点的使用。这是一个令人激动的时刻,我们可以开始安全地关闭API了!

需要注意的是,这些头信息是HTTP规范的草案。在最终确定之前,它们有可能会发生变化。也就是说,它们已经经历了几轮的修改,从现在开始,它们不太可能发生巨大的变化,现在可以广泛测试它们了。

不过这也意味着还有时间进行反馈! 如果你对它们的工作方式和如何更好地运行有想法的话,请与“Building Blocks for HTTP APIs”工作组联系。你可以给邮件列表发邮件:httpapi@ietf.org,或者在这里查看之前的邮件列表讨论。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注