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