QCon北京「鸿蒙专场」火热来袭!即刻报名,与创新同行~ 了解详情
写点什么

Stripe 面向未来的 APIs 版本控制方案

  • 2017-08-27
  • 本文字数:4125 字

    阅读完需:约 14 分钟

谈到 API,它的变更并不受人欢迎。对于软件开发人员来说,他们早已习惯了快速频繁的功能迭代;而 API 开发者却不一样,哪怕只有一位用户调用了 API,那么这个 API 想要改动就很麻烦了,它牵一发而动全身。我们中许多人都了解 Unix 操作系统的演化。1994 年, Unix-Haters 手册发布,其中包含很多有关该软件的邮件——内容无所不包,从专门针对 Teletype 机器而优化的、过度隐晦的命令名称,到不可逆的文件删除,再到选项过多的、不直观的程序本身。20 多年后,甚至是在众多的现代衍生系统中,这些吐槽中的绝大多数仍然适用。这是因为,Unix 的应用已经如此广泛,改变其行为影响巨大。但是,无论如何,它与客户订立的契约,定义了 Unix 接口的行为方式。

类似地,一个 API 代表了一份通信契约,没有通力的配合和大量的工作是无法修改的。由于许许多多的企业都将 Stripe 作为基础设施,所以我们从 Stripe 成立开始就一直在考虑这些契约。截至目前,我们要维护自 2011 年公司成立以来每个 API 的每个版本的兼容性。在这篇文章中,我们将分享在 Stripe 我们是如何管理 API 版本的。

编写代码集成 API 的过程中会加入某些固定的预期。如果一个端点返回一个名为verified的布尔型字段用于说明一个银行账户的状态,那么用户可能会编写如下代码:

复制代码
if bank_account[:verified]
...
else
...
end

如果我们后来使用一个status字段代替了银行账户的布尔型字段verified,由它包含verified的值(我们在 2014 年这样做过),那么上述代码就会被破坏,因为它依赖于一个此时已经不存在的字段。这种类型的变更不具备向后兼容性,是我们应该避免的。以前有的字段应该一直保留,而且类型和名称应该保持不变。不过,不是所有的变更都是向后不兼容的;例如,新增一个 API 端点,或者向一个已经存在但曾未用过的 API 端点添加一个新字段,这些都是安全的。

以通力合作为基础,我们也许能让我们的用户了解我们将要做出的变更,并让他们更新自己的集成代码,但即使可以这样做,也不是很友好的方式。就像电网连接或供水,在连接好之后,API 应该尽可能地保持运行不中断。

Stripe 的使命是提供互联网经济基础设施。就像电力公司不应该每隔两年就改变电压一样,我们认为,我们应该让用户相信,我们提供的 Web API 会尽可能地保持稳定。

API 版本控制方案

Web API 演进的一种常见方法是使用版本控制。用户在发出请求时指定版本,API 提供商可以根据需要修改下一个版本而又保持和当前版本兼容。当新版本发布后,用户可以在方便的时候升级。

这经常被视为一种主要的版本控制方案,将类似v1v2v3这样的名称作为 URL 前缀(如/v1/widgets)或者通过 HTTP 头(如Accept)传递。这是一种有效的方法,但是,其主要缺点是,版本之间的变化太大,对用户的影响也太大,其痛苦程度都快赶上重新集成了。这种方法也没有明显的优势,因为不愿意或无法升级的用户就被困在了旧版本上。这时,提供商就必须做出艰难的选择,是退役 API 版本,还是舍弃那些用户,或者付出相当大的代价没完没了地维护旧版本。虽然让提供商维护旧版本可能乍看之下对用户是有好处的,但是,他们也间接地付出了获得更新的速度下降的代价,因为工程时间花在了维护旧代码而不是开发新特性上。

在 Stripe,我们通过滚动版本实现版本控制,版本命名使用了 API 发布的日期(如2017-05-24)。虽然向后不兼容,但每个版本包含一小部分变化,这让增量升级变得相对容易,这样一来,集成就可以跟上版本更新的步伐。

用户第一次发起 API 请求时,他们的账户会自动钉选到最新的可用版本,之后,他们发起的每次 API 调用都会被隐式地分配到那个版本。这种方法可以确保用户不会突然接收到破坏性修改,并通过减少必要的配置让最初的集成少了些痛苦。用户可以手动设置Stripe-Version头,或者从 Stripe 控制板更新其账户钉选的版本,覆写任意单个请求的调用版本。

可能有读者已经注意到,Stripe API 也有使用前缀路径定义主版本(如/v1/charges)的情况。虽然我们确实会在某些时候使用这种方式,但是目前使用的方式在一段时间内将不会改变。如上所述,主版本变化往往会让升级很痛苦,而且,我们很难想象,一个 API 的重新设计重要到要让用户受到这种程度的影响。我们目前采用的方法已经支撑我们在过去六年中完成了将近 100 次向后不兼容的升级。

底层版本控制

版本控制总是要兼顾改善开发体验和维护旧版本的成本。我们努力实现前者,同时又最小化后者,并实现了一个版本控制系统帮助我们实现这一目标。让我们快速浏览一下它的工作原理。Stripe API 每一种可能的响应都被编写成类,我们称之为 API 资源。API 资源使用 DSL 定义可用的字段:

复制代码
class ChargeAPIResource
required :id, String
required :amount, Integer
end

API 资源被记录下来,其所描述的结构就是我们希望 API 的当前版本返回的内容。当我们需要做出向后不兼容的变更时,我们将其封装在一个版本变更模块中,其中定义了变更相关的注释、一个转换以及符合条件需要修改的 API 资源类型集:

复制代码
class CollapseEventRequest < AbstractVersionChange
description \
"Event objects (and webhooks) will now render " \
"`request` subobject that contains a request ID " \
"and idempotency key instead of just a string " \
"request ID."
response EventAPIResource do
change :request, type_old: String, type_new: Hash
run do |data|
data.merge(:request => data[:request][:id])
end
end
end

在主列表中为版本变更分配一个相应的 API 版本:

复制代码
class VersionChanges
VERSIONS = {
'2017-05-25' => [
Change::AccountTypes,
Change::CollapseEventRequest,
Change::EventAccountToUserID
],
'2017-04-06' => [Change::LegacyTransfers],
'2017-02-14' => [
Change::AutoexpandChargeDispute,
Change::AutoexpandChargeRule
],
'2017-01-27' => [Change::SourcedTransfersOnBts],
...
}
end

版本变更被记录下来,因此可以期望它们从当前的 API 版本按照顺序自动向后适用。但每次版本变更都会假设,“即使后续可能有新的变更,但它们收到的数据应该和该 API 最初编写出来时一样”。

在生成响应时,API 首先会通过描述当前版本的 API 资源来格式化数据,然后根据下面三项内容中的一项确定目标 API 的版本:

  • 如果提供了的话,则根据Stripe-Version头;
  • 如果请求是以用户的名义发送,则根据经过 OAuth 授权的应用程序的版本;
  • 根据用户钉选的版本,这是在用户首次向 Stripe 发送请求时设定的。

然后,我们会按照时间向前追溯,请求这个过程中找到的每一个版本变更模块,直到找到目标版本:

在返回响应之前,请求由版本变更模块处理

版本变更模块会将更旧的版本从核心代码路径中剔除出去。在构建新产品时,开发人员多半可以不考虑它们。

具有副作用的变更

大多数向后不兼容的 API 变更都会修改响应,但情况并非总是如此。有时候,可能需要进行比较复杂的变更,其范围超出了定义它的模块。我们为这些模块添加has_side_effects注解,它们定义的转换变成了空操作:

复制代码
class LegacyTransfers < AbstractVersionChange
description "..."
has_side_effects
end

在代码的其他地方需要对它们进行检查,看看是否还有效:

复制代码
VersionChanges.active?(LegacyTransfers)

这种弱化的封装让具有副作用的变更更加难以维护,因此,我们会极力避免。

声明式变更

自包含版本变更模块的其中一个好处是,它可以定义注释,说明它们影响的字段和资源。我们可以再次利用该注释快速向用户提供更多有用的信息。例如,我们的 API 变更日志是程序生成的,新版本的服务一部署,变更日志就会收到更新。

我们还针对特定的用户裁剪 API 参考文档。它知道谁登录了,并根据账户的 API 版本注释字段。这里,我们会警告开发人员,他们使用的 API 自钉选版本之后有向后不兼容的变更。Event 的request 字段之前是一个字符串,现在是一个还包含幂等键的子对象(在上述版本变更中产生的):

我们的文档会检测用户的 API 版本并发出相关警告

最小化变更

提供广泛的向后兼容性并不是免费的;每个新版本都意味着更多需要理解和维护的代码。我们尽力让我们编写的代码清晰,但是,如果整个项目里到处都是无法清晰封装、需要足够时间和大量检查的版本变更,则会延缓项目、降低可读性,让 API 变得更加脆弱。我们采用了一些度量指标,尽力避免招致这种昂贵的技术债务。

即使我们有可用的版本控制系统,我们还是尽可能地避免使用它,并设法在最初设计时保证 API 的正确性。输出变更是通过一个轻量级的 API 审核流程收集的,这些变更会被写入一个简单的支持文档中,并提交到邮件列表。这让每一个变更提案都可以被公司里更多的人看到,让我们可以在它们发布之前发现错误和不一致的地方。

我们一直都注意停用和使用的取舍。保持兼容性很重要,但即便如此,我们最终还是会希望开始退役旧的 API 版本。帮助用户迁移到 API 的新版本让他们可以利用新特性,同时也简化了我们构建新特性的基础。

变更的原则

滚动版本和支持这一机制的内部框架,这两者的结合让我们吸引了大量的用户,我们对 API 做了大量的变更,同时又将对现有集成的影响降至了最低。这种方式依赖于我们过去几年来总结出的一些规则。我们认为重要的——API 升级应该是:

  • “轻量级的(Lightweight)”。尽量降低升级成本(不管是对用户而言,还是对我们自己而言)。
  • “一等的(First-class)”。让版本控制成为 API 的一等概念,这样,可以用它保持文档和工具的准确和及时更新,并自动生成变更日志。
  • “成本固定的(Fixed-cost)”。通过将旧版本密封进版本变更模块来最小化维护成本。换句话说,在编写新代码时需要考虑的旧版本行为越少越好。

围绕 REST、GraphQL、gRPC 的争论及这些技术的发展让我们兴奋,更广泛地说,是 Web API 未来会发展成什么样子让我们兴奋,我们希望在接下来的很长一段时间内可以继续支持版本控制方案。

查看英文原文 APIs as infrastructure: future-proofing Stripe with versioning


感谢雨多田光对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们。

2017-08-27 19:002108
用户头像

发布了 1008 篇内容, 共 407.4 次阅读, 收获喜欢 345 次。

关注

评论

发布
暂无评论
发现更多内容

架构实战营模块一作业

maybe

Camtasia实用技巧之光标效果

淋雨

视频剪辑 专业录屏 Camtaisa

慧心购系统开发|慧心购软件APP开发

模块一作业

张文龙

#架构实战营

绿色篮子APP开发|绿色篮子软件系统开发

扩展Elasticsearch客户端简化ES查询(.net core/framework)

Spook

查看Linux端口占用,并关掉相关进程。

学神来啦

云计算 Linux 运维

架构实战营作业 M01

Shawn Liu

#架构实战营

一千零一次飞升APP系统开发内容

动手实操丨基于随机森林算法进行硬盘故障预测

华为云开发者联盟

机器学习 数据分析 modelarts 随机森林算法 硬盘故障

华云大咖说 | 安超虚拟化平台全“芯”亮点揭秘

华云数据

一亩金田APP开发|一亩金田软件系统开发

小萌猫短视频系统APP开发详情

架构训练营-模块一-作业

姑射仙人

#架构实战营

企业协作通过什么软件可以提高工作效率?

万事ONES

项目管理 敏捷开发 ONES 协同办公

揭秘版权保护下的视频隐形水印算法(上篇)

拍乐云Pano

视频云会议已成未来发展必然趋势

anyRTC开发者

音视频 WebRTC 视频会议 云视频

爬虫入门到放弃01:一句话概括什么是爬虫

叫我阿柒啊

Python 爬虫

一文带你读懂CNCF Landscape

焱融科技

分布式 云原生 高性能 云存储 cncf

Apache ShardingSphere 邀您相约 Open Source Day

SphereEx

教你几招HASH表查找的方法

华为云开发者联盟

数据结构 哈希表 hash 哈希函数

HarmonyOS开发者的喜讯——华为认证来啦!

科技汇

网络攻防学习笔记 Day67

穿过生命散发芬芳

网络攻防 7月日更

成为全栈式数据管控平台,CloudQuery 1.4 概述

BinTools图尔兹

可视化 数据安全 OpenAPI 数据迁移 数据库管理

你用哪些软件和方式来提高工作效率?

万事ONES

项目管理 ONES 敏捷管理

前端vue面试题,附答案

buchila11

Vue

2021能源发展极速前进,可视化赋能行业高效运作

一只数据鲸鱼

数据可视化 3D可视化 智慧能源 能源管理 光伏发电

架构实战营作业模块一

五只羊

架构实战营

拼拼有礼APP开发|拼拼有礼软件系统开发

哈哈生活系统开发|哈哈生活软件APP开发

接下来的40年,碳中和对我们来说意味着什么?

CECBC

Stripe面向未来的APIs 版本控制方案_语言 & 开发_Brandur Leach_InfoQ精选文章