编程

API 版本控制

323 2023-12-28 03:20:00

介绍

当今世界,API 无处不在。从你的商业应用到你最好朋友的线上商店,再到你的智能水壶,它可以用你手机上方便的应用程序煮咖啡。在某个时刻,所有东西都需要与其他东西交流,而作为软件工程师,这正是我们触碰 API 的地方。我们使用它向水壶发送更新,或为在线商店提供产品目录,或为您的业务应用可能需要的大量数据提供入口。

在过去的十年里,随着移动网络对更快的应用和网站的需求,API 变得越来越必要。这意味着,作为软件工程师,我们需要寻找在收到请求时快速为这些应用和网站提供数据的方法,而不是在应用或页面打开时。为了更好地理解这种转变,您可以深入研究 API 的历史,以及它们如何彻底改变了我们今天构建和与软件交互的方式

随着时间的推移,我们的 API 的复杂性不断增加,它们提供的功能也在不断增加,而且它们捕获的数据的足迹也在一次又一次地发生变化。这就是 API 版本控制可以帮助您在照常推进业务的过程中管理应用程序的增长和更改的地方。

例如,如果你为智能水壶提供 API,API 的变化可能意味着 100 或 1000 人突然无法在早上喝咖啡。或者人们无法在网上商店结账,导致销售额下降。它们没有从您的业务应用中获得正确或最新的信息,导致决策错误,并可能错过潜在客户。

正如您所想象的,不正确地对 API 进行版本控制可能会对您的业务或产品产生不利影响。

不过如何对 API 进行版本控制?
有什么方法?你真的需要对 API 进行版本化吗?也许您有一个内部应用使用 API,而只有一小部分人在非业务关键角色中使用该应用,您还需要对 API 进行版本化吗?让我们来看看这些问题。

基础:什么是 API 版本控制

因此,在我们开始之前,让我们深入了解什么是 API 版本控制。这是一种我们作为软件工程师将用来引入 API 更改的方法,就这么简单。我们可能想更改数据的结构或功能的工作方式,或者添加额外的授权层,以提高被忽视区域的安全性。我们还可以采取其他方法来完成其中的一些任务,例如基于角色的访问控制或功能标志。然而,通常我们希望将这些与 API的版本控制一起使用,以获得我们希望的应用程序的细粒度控制。

让我们来看看智能水壶的场景。我们可能会释放另一个使用相同 API的水壶,该水壶希望选择性地将水加热到不同的温度。我们最初的产品是将水加热到 100 度来制作咖啡,但从与客户的产品反馈来看,我们的产品主要用于那些只想将水加热至 80 度来制作完美一杯茶的茶客。这本身可能是一篇关于制作一杯完美茶的单独文章。我们最初的产品使用了 API 的第 1 版,只允许将水加热到 100 度。我们可以对此进行修改,并添加 100 度为默认温度,但这不允许我们为想要该选项的用户添加定制。

API 版本控制

如果我们在 API 中实现了版本控制,我们的新产品发布可以与 API 的新版本的发布同步,从而允许所有新水壶直接与所需的新端点对话。然后,我们可以错开对旧产品的更新,将其升级为使用新的 API,从而为所有拥有受支持产品的客户提供新功能。允许拥有不受支持的产品的客户一如既往地使用该产品。拥有这种无缝兼容性意味着我们的客户将对我们的品牌拥有最好的体验,从而增加他们未来成为回头客的可能性。

如果我们不版本化我们的 API——我们最终可能会失去客户忠诚度和信任。虽然这可能是不版本化 API 的极端情况,但这确实可能发生。即使你没有立即发布额外产品或更改功能的计划,你想在以后添加这个巨大的新功能时担心吗?还是想更改最初帮助你扩大用户群的大量功能?

API 版本控制类型

像咖啡一样,API 版本有多种不同的口味。你需要找到最适合你的,最适合你实际场景的,这样你就不用费劲力气才能达成目标。

URL 版本控制

最流行的方法之一是 URL 版本控制。在这里你会看到这样的东西 https://api.domain.com/v1/some-resource 在你的 URL 中。这有时也被称为路径版本控制。这里的想法是允许用户通过修改 URL 本身来选择他们想要轻松访问的版本。这种方法有很多好处。它提供了开箱即用的简单性,允许你在同一域名中简单地嵌套 API 的新版本,而无需任何额外的思考。你甚至可以将其托管在 Nginx 反向代理之后,以允许不同的服务器托管不同版本的 API。然而,它也并非没有缺点。

使用通过 URL 版本控制的 API 版本意味着,无论您选择使用哪种编程语言或框架,都很容易导致重复代码。你在复杂性之上添加复杂性,并通过一个入口点(URL)将其绑定在一起。现在,如果你有时间的话,这是你可以解决的问题,但这不是最真实的期望。

媒体类型版本化控制

虽然没有 URL 版本控制那么受欢迎,但自从 GitHub 等公司改用这种方法以来,它的受欢迎程度一直在提高。通过这种方法,我们使用请求 Accept 头来确定要与哪个版本的 API 通信。这确实增加了对谁可以更改或更新版本的限制,因为并不是每个人都可以更改请求标头,或者知道它们应该如何格式化。如果我们要遵循 API 标准,那么我们希望看到像 Accept: application/vnd.api.version+json 这样的东西,其中 version 是所请求的版本。如果我们要构建一个完全 RESTful 的 API,那么我们已经在进行内容协商,以确定用户希望如何发送响应。然而,将版本控制的额外复杂性添加到其中会使高效构建 API 变得更加困难。

查询参数版本化控制

查询参数是我们很多人都熟悉的东西,然后是 API,但对于版本控制来说就不那么熟悉了。使用查询参数进行版本控制的好处是易于实现,并且可以随时更改。查询参数非常灵活,在所有 web 和应用程序客户端都有极好的支持。就像媒体类型的版本控制一样,这可以在您已经构建的基础上增加额外的复杂性层。除了内容协商、数据过滤和排序之外,将 API 版本控制添加到查询参数中,还添加了另一件您必须进行逻辑检查的事情,此时您应该更多地关注于微调想要返回的响应。

最佳实践

在软件工程中,我们有各种标准,从如何编写代码到如何编写测试。当涉及到 API 版本控制时,标准没有任何不同。当涉及到API 时,拥有一个标准是很重要的,它鼓励开发人员遵守你打算向客户提供的约束。

无论采用哪种方法进行 API 版本控制,重要的是确保保持一致并记录该方法。这让用户相信,随着应用的发展,API 版本不会有任何变化。

实现

在实现 AP I版本控制时,你需要考虑哪种方式最适合你的产品和客户。你有一个技术含量很高的团队,而客户列表的技术含量较低?也许你没有一个过于技术化的团队,但有一个非常技术化的客户列表。你必须考虑所有的选择,以决定什么是有效的。不仅仅是现在,你还需要在五年后考虑你的产品。我一直追求的产品开发规则是思考明天的产品,而不是你现有的产品。让我们浏览一下选项,并讨论每种方法的不同优点和缺点。

让我们从我最不喜欢的选项开始吧。查询参数版本控制方法。我不会关注任何特定的语言或框架,我会更多地关注 “sudo coe”,这样这个想法是通用的。

public function index(request)
  switch (request.get('version')):
    case 'v1':
      data = this.service.v1.listall()
    case 'v2':
      data = this.service.v2.listall()
  
  return response.data(data)

这是一种我们可以直接在控制器中实现的方法,我们可以根据查询参数中的版本热交换数据存储库。这使我们能够为 API 保留一个中心入口点,同时为如何在后台检索数据提供了灵活的方法。然而,正如我之前提到的,它并不完美——查询参数被设计用于过滤和排序应该返回的数据。让我们继续前进。

接下来我们将选择最简单的选项,URL 版本控制。我们有多种选择,无论我们使用单一的代码库还是后面有小型微服务的 API 网关。过去最常见的是单一的代码库,但随着微服务的普及,API 网关已成为一种流行的方法。然而,API 网关中的版本控制相对容易,它足够灵活,只需很少麻烦就可以支持它。让我们看一个例子,只是使用它作为一个单一的代码库。

public function handle(request, next): response
  switch (request.path.matchesPattern('v')):
    case 'v1':
        app.container.bind(data-repository, v1.data-repository)
    case 'v2':
        app.container.bind(data-repository, v2.data-repository)

   return next(request)

这是一种非常类似于我们如何使用查询参数的方法,其中我们有一个中间件,它将捕获版本并在依赖于版本的容器中重新绑定我们的数据存储库。我们这样做的原因是,虽然每个路由可能有不同的控制器,但我们希望数据存储库特定于所请求的版本。这里最大的缺点是代码重复。您必须拥有多个控制器和存储库,它们在产品向前发展时得到维护。因此,实际上你必须保持一定水平的技术债务,以保持向后兼容性。这在软件世界中并不罕见,但我认为我们都同意,这不是最理想的情况。

最后,让我们讨论一下什么可能是最难的方法,Header 版本控制。这在单体中不太常见,因为这很难做到,但在 API 网关中,这几乎是为了完美工作而设计的。像 GitHub 这样的公司使用 Header 和 content type 来切换其 API 的版本。在这里使用 API 网关,你可以切换要将请求发送到哪个服务或 API,这使您的应用代码更加简单。对于 sudo 代码示例,我假设你没有使用 API 网关。

public function handle(request, next): response
  switch (request.headers.get('Content-Type')):
    case 'application/vnd.api.v1+json':
        app.container.bind(data-repository, v1.data-repository)
    case 'application/vnd.api.v2+json':
        app.container.bind(data-repository, v2.data-repository)

   return next(request)

此处我们想做一些与处理 URL 版本控制非常相似的事情,这是我个人发现的最可靠、最可测试的版本控制方法。

文档及交流

在对 API 进行版本控制时,你需要确保每个版本都有完整的文档,并且可以轻松地在版本之间切换。您需要为用户提供一种方式,让他们轻松决定要使用哪个版本以及为什么要使用。他们需要很容易地看到哪个版本做了什么,以及有什么区别。沟通每个版本之间的差异对于 API 的成功至关重要。然而,这不仅仅是关于文档——你需要有好的文档。Laravel(一个 PHP 框架)之所以广受欢迎,其中一个原因是它的文档记录得很好。拥有面向用户的可读文档,指导他们完成 API 的使用之旅几乎是至关重要的。简单地依靠代码或 OpenAPI 规范自动生成的文档是构建文档的最快方法,这非常简单,非常糟糕。没有人想读它,也没有人必须读它。然而,当文档付出了努力时,阅读它可能会非常愉快,而不是试图找到不阅读文档的理由。

REST API 版本控制

在构建 REST API 时,是否已然假设您将遵循某种版本控制策略。在设计 API 时,您需要考虑哪种方法最适合您。有时需要对 API 进行版本化,因此需要思考哪种方法最适合你的产品。然而,有时你实际上并不需要版本控制——在这种情况下,你可以放松。

GraphQL API 版本控制

当涉及到 GraphQL API 时,您对版本控制的处理方式完全不同。GraphQL 有一个 @deprecated 指令,你可以将其添加到 schema 定义中,从而可以在 API 中传达即将发生的修改。它的灵活性和特定于查询的响应允许使用更细粒度和客户端驱动的方法来管理修改。然而,这确实会将破坏性更改的风险转移到客户端身上。这种方法倾向于提供更平稳的转换,减少版本控制冲突,并确保旧客户端不会因更改而中断。然而,它也需要仔细的模式设计和演变,以及与客户端的清晰沟通,以确保随着时间的推移,弃用的字段会逐步淘汰。

持续集成

CI 中的 API 版本控制为开发团队带来了一些独特的挑战,但也带来了机遇。CI 促进了频繁地将代码更改合并到中央存储库中,然后进行自动化构建和测试。当 API 发生任何类型的更改,尤其是任何突发性更改时,它都可能对任何依赖的组件或服务产生巨大影响。在 CI 环境中,未能正确版本化 API 可能会导致集成失败,尤其是在用户未做好准备或未通知你要发布的任何更改的情况下。因此,当你的计划快速推进时,集成版本策略是至关重要的。这可以通过清晰的沟通、良好的文档、活跃的社区以及使用最现代的标准和方法来避免。但是,你需要确定哪种方法(如果有的话)适合你。它不仅仅是简单地在你的 URL 中粘贴一个 v2,因为在你知道之前,你就已经上了 v10,没有回头路了。这里学到的教训是,设计你的 API 是有目的的,这个目的应该始终适合你的用户。

安全考虑

当涉及到 API 版本控制时,很多人没有考虑的一件事是安全性问题。虽然 API 的版本控制主要关注 API 的功能和兼容性,但它也带来了你需要考虑的重要安全影响。随着 API 的发展。某些版本可能包含在较新版本中修复的漏洞。如果没有正确的版本控制和生命周期管理,API 的过时版本可能仍然暴露在这些漏洞中。这些可以作为任何攻击者的潜在入口,他们可能会选择作为目标。当 API 版本被弃用时,通知用户并为他们提供干净的迁移路径以更新集成是至关重要的。通知他们为什么要弃用,是你们对这些事情有把握的标志,而不是你们写了错误代码的标志。维护 API 的多个版本增加了安全审计和渗透测试的复杂性,也扩大了 API 本身的攻击面。版本控制确保了 API 中的功能兼容性,但它也需要提高警惕的安全治理,以防止潜在的漏洞在你前进的过程中残留在过时的版本中。

性能影响

让我们来谈谈 API 版本控制对性能的影响。在构建 API 时,我们会考虑如何扩展产品。如何支持用户的突然增加,或者如何在不造成瓶颈或技术债务的情况下扩展功能。如果团队扩充 5 倍,我们该怎么做?但我们从未想过 API 版本控制策略可能会导致任何性能问题,或者它们可能会加剧我们所看到的现有性能问题。版本控制可确保兼容性和功能进展,还可以为你和用户引入性能考虑因素。维护 API 的多个版本意味着潜在地在单体代码库中维护各种代码库或分支逻辑,以处理不同的版本请求。这种分支可能会增加处理请求的开销,尤其是在版本之间存在显著差异或确定版本和执行代码的逻辑复杂的情况下。我们最终建造了一座纸牌屋,别无选择,只能继续堆叠。对使用而言,使用非标准的 API 版本可能会导致接收到比所需更多的数据,或者可能是效率较低的格式。这会影响数据解析和处理的速度。缓存策略变得更加复杂,因为不同版本中的同一资源可能需要单独缓存。虽然版本控制对性能的影响会因策略而异,但不可否认的是,API 的版本控制会引入可能难以解开或维护的复杂层。

挑战

当你第一次看 API 版本控制时,你将面临的最大挑战是决定你是否应该使用 API 版本控制,如果应该,应该使用哪种策略。虽然它对于确保兼容性至关重要,但如果在 API 中采用版本控制,它确实会带来一系列挑战。你将面临的主要障碍之一是决定何时增加版本。一个小的、非破坏性的更改是否应该保证一个新版本?还是应该只针对重大的突破性更改增加版本?找到正确的平衡可能很困难,同时维护多个版本可能会导致代码库臃肿,并增加开发人员的开销。他们需要确保在所有支持的版本中都能修复错误。将 API 中的更改传达给用户带来了另一个挑战。他们需要随时了解弃用、新版本以及可能影响其与服务集成的任何更改。应该支持旧版本多长时间?这方面没有规则,也没有指导方针,世界各地的每个人都在做一些稍微不同的事情。无论采用哪种方法对 API 进行版本控制,都会极大地影响 AP I的易用性,以及用户适应潜在变化的容易程度。让最初选择是否以及如何版本 API 变得更加困难。

未来趋势

随着技术环境的变化,围绕 API 版本控制的不同策略和方法可能会发生变化。需要考虑的一个趋势是向后兼容的更改,其目的是通过利用 GraphQL 等技术来最大限度地减少对新版本的需求。这允许你在不中断现有集成的情况下引入新功能或字段。另一个需要考虑的是使用 gRPC,这是一种基于契约的版本控制,其中 API 是围绕特定契约设计的。当约束发生变化时,更容易识别破坏性的变化并相应地调整版本。在 API 版本控制方面,自动化工具和 AI 可能在未来几年发挥更大的作用,预测变化对依赖系统的影响,并提醒开发人员潜在的冲突。微服务虽然不是目前最热门的话题,但可能会再次流行起来,去中心化的版本控制方法允许对服务和集成进行细粒度控制,使它们能够相互独立地移动。虽然 API 版本控制的基本策略将与我们保持一致;工具、实践和技术都已准备好适应 API 版本控制的下一阶段。

总结:掌握 API 版本以实现无缝增长

掌握 API 版本控制对于确保 API 的无缝增长至关重要。随着产品的发展和扩展,底层 API 需要适应新的功能、改进和更改,而不会干扰用户和现有集成。有效的 API 版本控制确保了向后兼容性,使较旧的应用即使在较新的应用利用更高级的功能时也能正常工作。它需要在引入新版本的同时维护旧版本,同时保持代码的清洁和可维护性之间取得谨慎的平衡。围绕版本更改、更清晰的文档和可理解的弃用警告进行战略沟通,将进一步帮助你增强用户的体验。最终,通过掌握 API 版本控制的复杂性,你可以加快创新的步伐,确保增长和进化可以在产品中共存,同时产生摩擦。

 

API