NVIDIA 初创加速计划,免费加速您的创业启动 了解详情
写点什么

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:001668
用户头像

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

关注

评论

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

KaiwuDB 成功入选《2023 ToB 行业影响力价值榜 · 创新力产品榜》

KaiwuDB

数据库

TCL实业盘古实验室发布全域光晕控制等多项创新显示技术

Geek_2d6073

我们是如何测试人工智能的(七)智能客服系统拆解与测试方法

测试人

人工智能 软件测试

一文读懂模块化赛道新的头部公链Meta Earth

加密眼界

数仓调优实战:GUC参数调优

华为云开发者联盟

数据库 华为云 华为云开发者联盟 华为云GaussDB(DWS) 企业号2024年4月PK榜

我们是如何测试人工智能的(二)数据挖掘篇

测吧(北京)科技有限公司

测试

提升团队工程交付能力,从“看见”工程活动和研发模式开始

阿里巴巴云原生

阿里云 云原生 云效

企业级依赖管理: 深入解读 Maven BOM

LightGao

maven 设计模式 架构设计 软件系统 java 架构

Digital Realty 将人工智能驱动的能效平台扩展至亚太地区

财见

Sam Altman 联手苹果前首席设计官打造 AI 设备;特斯拉将推出无人驾驶出租车丨 RTE 开发者日报 Vol.178

声网

浪潮信息发布全球首个单存储16节点SAP HANA集群方案

财见

我们是如何测试人工智能的(六)推荐系统拆解

测试人

人工智能 软件测试 自动化测试 测试开发

亚马逊店铺引流:海外云手机的利用方法

Ogcloud

云手机 海外云手机 云手机海外版 国外云手机 美国云手机

【IoTDB 线上小课 01】我们聊聊“金三银四”下的开源

Apache IoTDB

建设智慧公厕有什么好处?都有哪些功能?

光明源智慧厕所

Mistral Large模型现已在Amazon Bedrock上正式可用

财见

天翼云超大规模高性能云基础底座、“息壤”获国资委权威认可!

编程猫

为什么中小企业普遍选择IT运维外包了?

Ogcloud

IT运维 IT外包 IT外包公司 IT外包服务 IT运维外包

一文读懂模块化赛道新的头部公链Meta Earth

大瞿科技

广东智慧公厕管理系统哪家好

光明源智慧厕所

2024年智慧厕所解决方案,光明源智能科技是怎么实现的。

光明源智慧厕所

我们是如何测试人工智能的(三)数据构造与性能测试篇

测吧(北京)科技有限公司

测试

我们是如何测试人工智能的(七)包含大模型的企业级智能客服系统拆解与测试方法 – 知识引擎

测试人

人工智能 软件测试 自动化测试 测试开发

今日分享丨单点登录原理及OAuth20授权码协议

inBuilder低代码平台

低代码 单点登录

5个为什么要做外贸网站推广的理由

九凌网络

解锁ETLCloud中Kettle的用法

RestCloud

kettle 数据同步 ETL 数据集成

DACI决策框架,给低效能企业一个机会

填空时光

项目管理 效能提升 效能工具 决策管理

IT外包服务助推企业产业融通

Ogcloud

IT IT外包 IT外包公司 IT外包服务 IT外包企业

和鲸科技入选 2023 年度中国高科技高成长企业系列榜单丨第一新声 & 天眼查

ModelWhale

大数据 #人工智能 人工智能公司

2024 年“和鲸杯”辽宁省普通高等学校本科大学生计算机设计竞赛启动会圆满结束!

ModelWhale

人工智能 大数据 大学生竞赛

我们是如何测试人工智能的(四)补充:模型全生命周期流程与测试图

测吧(北京)科技有限公司

测试

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