
那么,给一个API版本化有多难呢?真相是这并不难,但困难在于保持某种理智,而不是不必要地陷入数量繁多、相互不兼容的版本和子版本中,这些版本和子版本应用于数十个API端点。
Business in a box.
探索我们的解决方案。
与我们的销售团队交谈
重大变化不好!API版本控制好!
任何曾经构建或经常使用API的人迟早都会意识到,重大变化非常糟糕,可能会对一个本来有用的API造成非常严重的瑕疵。重大变化是指对API行为的改变,可能会破坏用户的集成,并导致API提供者与用户之间的许多挫折感和信任丧失。重大变化要求用户提前通知(并伴随致歉),而不是像令人愉悦的新功能那样突然出现。避免这种挫折的方法是通过API所有者的保证为API制定版本,以确保任何单一版本中不会引入令人惊讶的变化。
那么,给一个API制定版本有多难呢?事实上并不难,但难的是在不必要地陷入令人头晕的版本和子版本数量中保持理智,这些版本和子版本应用于几十个API端点,上面的兼容性不明确。
我们在三年前推出了API的v1,并没有意识到它能够一直保持至今。那么,我们在两年多的时间里如何继续提供最佳的电子邮件传送API,同时保持相同的API版本呢?虽然关于如何给REST API制定版本有很多不同的观点,但我希望我们谦逊而强大的v1的故事能够在您的API版本化启蒙之路上指引您。
REST Is Best
API 管理
选择版本控制约定后,我们又有了更多问题。何时应该提升版本号?什么是重大更改?我们是重新版本化整个 API 还是只是某些端点?在 SparkPost,我们有多个团队在不同部分的 API 上工作。在这些团队中,人们在不同时间处理不同端点。因此,确保我们的 API 在使用约定时保持一致性非常重要。这超越了版本控制的范畴。
我们成立了一个治理小组,包括代表每个团队的工程师、产品管理团队的一名成员以及我们的 CTO。这个小组负责在所有团队中建立、记录并执行我们的 API 约定。一个 API 治理 Slack 频道对于就此话题进行生动的辩论也很有帮助。
治理小组确定了多种可以引入 API 的更改,既对用户有利又不构成重大更改。这些包括:
新的资源或 API 端点
新的可选参数
对非公开 API 端点的更改
JSON POST 主体中的新可选键
JSON 响应主体中返回的新键
相反,重大更改包括任何可能破坏用户集成的内容,例如:
新的必需参数
POST 主体中的新的必需键
现有端点的移除
现有端点请求方法的移除
API 调用的实质性不同的内部行为,例如默认行为的更改。
The Big 1.0
当我们记录和讨论这些约定时,我们也得出结论,避免对API进行重大更改符合每个人(包括我们自己!)的最佳利益,因为管理多个版本会增加相当多的开销。我们决定在承诺“v1”之前,有一些我们应该修复的API问题。
发送简单的电子邮件需要过多的努力。为了“保持简单事物的简单”,我们更新了POST主体,以确保既适合简单的又复杂的用例。新的格式在未来也更加可靠。其次,我们解决了Metrics端点的问题。该端点使用了一个“group_by”参数,该参数会更改GET响应主体的格式,使得第一个键是group by参数的值。这看起来不太像RESTful,所以我们将每个group by拆分成一个单独的端点。最后,我们审核了每个端点,并在各处做了一些小改动,以确保它们符合标准。
准确的Documentation
拥有准确且可用的 API 文档很重要,以避免有意或无意的重大更改。我们决定使用一种简单的 API 文档方法,利用一种名为 API Blueprint的Markdown语言,并在Github上管理我们的文档。我们的社区参与协作并改进这些开源文档。同时,我们还在 Github 中维护了一套非公开的内部API和端点文档。
起初,我们将文档发布到 Apiary,这是一款用于原型设计和发布API文档的出色工具。然而,将 Apiary 嵌入我们的网站在移动设备上不起作用,因此我们现在改用Jekyll来生成静态文档。我们的最新SparkPost API 文档现在加载迅速,并在移动设备上运行良好,这对于并不总是坐在电脑前的开发者来说非常重要。
分离 Deployment 和 Release
我们很早就学到了一个宝贵的技巧,即将部署与发布分开。这样一来,当变更准备好后,可以通过持续交付和部署频繁地进行部署,但我们不总是同时公开宣布或记录这些变更。对于我们来说,在UI中或使用内部工具时,先部署一个新的API端点或对现有API端点的增强,而在公开记录和支持之前使用它,这并不罕见。这样,我们可以对其进行一些调整以提高可用性或符合标准,而不必担心造成令人害怕的重大变更。一旦我们对更改感到满意,我们就会将其添加到我们的公共文档中。
哎呀!
我们必须承认,有些时候我们未能坚持我们的“无破坏性更改”理想,这些都是值得学习的。在一次情况下,我们认为如果某个属性默认设置为 true 而非 false 会对用户更好。在我们部署更改后,由于行为发生了意外变化,我们收到了用户的几次投诉。 我们撤销了更改并增加了一个账户级别设置——这显然是更为用户友好的方法。
有时,我们会因为修复错误而想引入破坏性更改。然而,为了保持一致性,我们决定不改变这些特性,以免风险破坏客户的集成。
在极少数情况下,我们做出了重要决定去进行破坏性更改——例如弃用一个 API 资源或方法——为了更广泛用户社区的利益,并且只有在确认对用户几乎没有影响的情况下才进行。例如,我们特意选择改变 Suppression API 的响应行为,但只是在仔细权衡对社区的好处和影响,并认真与我们的用户沟通更改之后。然而,我们绝不会引入对用户的生产电子邮件发送有直接影响的更改,即使可能性极低。