
那么,给一个API版本化有多难呢?真相是这并不难,但困难在于保持某种理智,而不是不必要地陷入数量繁多、相互不兼容的版本和子版本中,这些版本和子版本应用于数十个API端点。
重大变化不好!API版本控制好!
正如任何构建或经常使用API的人迟早会意识到的那样,重大更改非常糟糕,并可能是对其他有用的API上的一个非常严重的瑕疵。重大更改是对API行为的更改,可能会破坏用户的集成,并导致许多挫折和API提供者与用户之间信任的丧失。重大更改要求提前通知用户(附带道歉),而不是像一个令人愉悦的新功能那样的突然更改。避免这种挫折的方法是对API进行版本管理,并从API所有者那里保证在任何单个版本中都不会引入令人惊讶的更改。
那么,对一个API进行版本管理究竟有多难?事实是并不难,但难的是通过不必要地陷入晕眩数量的版本和子版本中而保持某种理智,这些版本和子版本被应用于数十个API端点且兼容性不明。
我们在三年前推出了v1的API,没有意识到它会持续到今天。那么,我们是如何在超过两年时间内继续提供最佳的电子邮件传递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 docs现在加载迅速,并在移动设备上效果良好,这对开发人员来说很重要,因为他们并不总是坐在电脑前。
分离 Deployment 和 Release
我们很早就学到了一个宝贵的技巧,即将部署与发布分开。这样一来,当变更准备好后,可以通过持续交付和部署频繁地进行部署,但我们不总是同时公开宣布或记录这些变更。对于我们来说,在UI中或使用内部工具时,先部署一个新的API端点或对现有API端点的增强,而在公开记录和支持之前使用它,这并不罕见。这样,我们可以对其进行一些调整以提高可用性或符合标准,而不必担心造成令人害怕的重大变更。一旦我们对更改感到满意,我们就会将其添加到我们的公共文档中。
哎呀!
必须承认,有时候我们没有完全遵循我们的“无破坏性更改”理想,这些情况值得我们学习。在一次情况下,我们认为如果一个特性默认设置为true而不是false,对用户来说会更好。我们部署更改后,收到了用户的多次投诉,因为行为意外发生了变化。 我们撤销了更改,并添加了一个账户级别的设置——这无疑是一种更加用户友好的方法。
有时我们希望通过修复错误来引入破坏性更改。然而,我们决定保留这些特性独特性,而不是为了一致性而冒着破坏客户集成的风险。
极少情况下,我们会做出关键决定,做出破坏性更改——比如弃用一个API资源或方法——这是为了更广大用户社区的利益,并且只有在确认对用户几乎没有影响后才会进行。例如,我们有意识地选择改变了Suppression API的响应行为,但这是在仔细权衡对社区的好处和影响并且认真与用户沟通过更改后才实施的。然而,我们 绝不会 引入任何可能直接影响用户生产邮件发送的更改。