HarmonyOS开发者限时福利来啦!最高10w+现金激励等你拿~ 了解详情
写点什么

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

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

关注

评论

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

我以订披萨为例,给女朋友详细讲了Java设计模式的3种工厂模式

华为云开发者联盟

Java 工厂模式 工厂方法模式 简单工厂模式 抽象工厂模式

MySQL分区表简介

soap said

:MySQL 数据库

Hadoop小文件问题产生及解决方案

五分钟学大数据

hadoop 1月月更

中国边缘云基础设施服务市场份额,百度智能云第一

百度开发者中心

边缘计算 边缘云

基于端到端的实时计算加速技术

鲸品堂

软件 通信运营商

工具 | 常用 PostgreSQL 预防数据丢失方案

RadonDB

数据库 postgresql 后端 RadonDB

好用易用的主机监控系统哪里有?主要监控指标包含哪些?

行云管家

运维 IT运维 主机监控 网络监控

一起玩转LiteOS组件:Opus

华为云开发者联盟

编码 LiteOS Opus LiteOS组件 pcm

3DCAT首届行业生态交流会|燧光CTO 戴景文:云渲染 XR大发展的助推器

3DCAT实时渲染

云计算 XR VR/AR 渲染

使用php-amqplib实现RabbitMq

Owen Zhang

php RabbitMQ php-amqplib

在线时间加减计算器

入门小站

工具

作业2

dll

百度专网地图,助力中国联通智慧运营

百度开发者中心

地图 智慧城市

3DCAT首届行业生态交流会|爱智慧科技有限公司CEO梁新刚:工业元宇宙的”形“与”神“

3DCAT实时渲染

XR VR/AR 渲染 元宇宙

2022年企业数字化技术应用10大趋势

易观分析

银行数字化转型 企业数字化 后疫情时代企业应对策略 十四五规划

流处理组件Flume使用攻略

编程江湖

18 Prometheus之PromQL介绍

穿过生命散发芬芳

Prometheus 1月月更

Spock框架Mock对象、方法经验总结

FunTester

Mockito powermock Mock spock FunTester

低代码实现探索(二十三)假如你会些游戏剧情脚本

零道云-混合式低代码平台

知识中台,驱动产业智能化升级

百度开发者中心

知识图谱

专注实时云渲染技术,3DCAT赋能行业伙伴云化转型

3DCAT实时渲染

云计算 VR/AR 渲染

百度专网地图,助力中国联通智慧运营

百度大脑

人工智能

Java正则表达式语法大全

编程江湖

『内存中的操作系统』虚拟化是什么

soolaugust

操作系统 内存

明道云实现产品BOM清单的快速搭建

明道云

组件通信、硬件池化,这些创新技术你get了吗?|HDC2021技术分论坛

HarmonyOS开发者

HarmonyOS

大数据开发之通过 Spark 来扩展 Presto

@零度

大数据 spark

Linux之diff命令

入门小站

Linux

专访 | 滴滴青桔资深设计师首次揭秘“实时云渲染+云展厅”设计

3DCAT实时渲染

云计算 云渲染 云展厅

作业一

dll

作业3

dll

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