发布于 3年前

如何对REST API进行版本控制

如果您对API不太熟悉,您可能会想...为什么对API版本控制大惊小怪?

如果您对API的更改感到厌倦,那么您可能会大惊小怪。如果您是API的维护者,那么您可能还会大惊小怪地尝试解决诸如此类的难题:

# 下面的v2(版本2)表示的只是产品版本还是整个APId呢?
/v2/products
# 是什么促使v1和v2的更改呢? 它们之间有什么不同呢?
/v1/products
/v2/products

这些有关版本控制的问题都不容易回答。有时并不总是很清楚什么是v1或者v2。而且,当第一个版本看似不能够满足时,我们不应该就新建第二个版本。

是有明确的理由,为什么你的API需要有版本,并有清晰的策略去有效地引导API的变化。

但是,我发现大多数开发人员(包括我自己),直到我以艰难的方式学到了一些教训后,才意识到这些原因和策略。

本文旨在突出版本控制的原因以及实现它的策略。我们将假设是使用REST API,因为它是很多API的标准,然后重点讨论它的版本控制。

什么是版本控制?

我们应该从术语“ API版本控制”的级别设置开始。这是我们的定义:

API版本控制是透明管理API更改的做法。

版本控制是围绕API更改进行的有效沟通,因此,消费者知道可以从中获得什么。假如你正在以某种方式对外传递数据,当你更改数据的传递方式时,是需要对外进行沟通。

归根结底,这就是管理数据合约和破坏变更(break change)。前者是API的主要构建块,而后者则说明了为什么需要版本控制。

数据合约

API是应用程序编程接口(Interface),而接口是交换信息的共享边界。数据合约是此接口的核心。

数据合约是关于请求和/或响应数据的格式以及内容的协议。

为了说明数据协定,这是一个基本的JSON响应体:

{
  "data": [
    {
      "id": 1,
      "name": "Product 1"
    },
    {
      "id": 2,
      "name": "Product 2"
    }
  ]
}

对象中包含了data属性,它是一个表示产品的数组(列表),每个产品有属性id和name。但是属性data可以改为body属性,每个产品的属性id可以是GUID而不是整数。如果要返回单个产品,则data可以是一个对象而不是一个数组。

对于数据的“形状”,这些看似微妙的变化将导致不同的协议,不同的约定。数据形状可以体现在属性名称,数据类型,甚至可以是想要的格式(JSON与XML)。

为什么需要版本控制?

使用API​​时,就是简单的把属性名productId改为productID,对消费者来说也是破坏性的更改。我们的团队上周就发生了这件事。

幸运的是,我们进行了测试,发现了这个api协定的更改。但是,我们不应该需要那些测试,因为API的维护者应该知道这将是一个重大变化。

重大更改(Breaking changes)

这是对约定的数据合约的重大更改,这些更改会让我们也更改应用程序。

什么是API的“重大更改”?你对API约定的任何更改都迫使消费者也进行更改。

重大更改主要包括以下几类:

  1. 更改请求/响应格式(例如,从XML到JSON)
  2. 更改属性名称(例如从name到productName)
  3. 在请求上添加必填字段(例如,请求正文中新的必填标头或属性)
  4. 删除响应中的属性(例如,description从产品中删除)

API变更管理

强迫API使用者进行更改永远是不明智的选择。如果您必须进行重大更改,那就是版本控制的目的,我们将介绍对应用程序和端点(endpoint)进行版本控制的最有效方法。

但是首先让我们简要地讨论一下如何避免破坏更改。我们可以称之为API变更管理。

以下原则概括了API的有效变更管理:

  • 继续支持现有的属性/端点(endpoint)
  • 添加新属性/端点(endpoint),而不是更改现有属性/端点
  • 考虑过时的属性/端点

这是一个示例,它在请求用户数据的响应中展示了所有这三个原理:

{
  "data": {
    "id": 1,
    "name": "Carlos Ray Norris",     // 原属性
    "firstName": "Carlos",           // 新属性
    "lastName": "Norris",            // 新属性
    "alias": "Chuck",                // 过时的属性
    "aliases": ["Chuck", "Walker"]   // 新属性
  },
  "meta": {
    "fieldNotes": [
      {
        "field": "alias",
        "note": "Sunsetting on [future date]. Please use aliases."
      }
    ]
  }
}

在此示例中,name是原始属性。如果消费者希望通过一些字符串插值来显示“ Norris先生”,而不必解析该字段,则需要将firstName和lastName字段分开来提供更细name选项。但是,新格式需要继续支持name属性。

alias是另一种情况,它将被废弃,推荐使用aliases数组,因为Chuck的别名很多,在响应中添加注释,说明alias的废弃时间。

您如何版本化API?

这些原则在不需要更改版本的情况下,引导API的更改需要花费很长的时间。但是,有时这是可以避免的,并且如果你需要全新的数据约定,则需要新版本的端点(endpoint)。因此,你需要以某种方式将其传达给公众。

顺便说一句,请注意,我们并不是在谈论基础代码库的版本。因此,如果你正在为还支持公共API的应用程序使用语义版本控制,则可能需要将这些版本控制系统分开。

如何创建API的新版本?这样做有哪些不同的方法?您需要确定总体上要采用哪种类型的版本控制策略,然后在开发和维护API时,需要确定每个版本更改的范围。

范围(Scop)

让我们先解决范围。正如我们上面所探讨的,有时数据合约会因重大更改而受到损害,这意味着我们需要提供新版本的数据合约。这可能意味着端点(endpoint)的新版本,或者可能意味着在更全局的应用程序范围内进行了更改。

我们可以在树的类比中考虑范围变化的级别:

  • 叶子(Leaf ) -更改为隔离的端点,与其他端点没有关系
  • 分支(Branch ) -更改为一组端点或通过多个端点访问的资源
  • 主干(Trunk ) -应用程序级更改,保证大多数或所有端点上的版本更改
  • 根(Root) -更改影响对所有版本的所有API资源的访问

如您所见,从叶到根,这些变化变得越来越有影响力,并且范围越来越广。

该叶范围通常可以通过有效的API变更管理处理。如果没有,只需使用新的资源数据协定创建一个新的端点。

一个分支是有点麻烦,这取决于有多少端点由有问题的资源数据契约变化的影响。如果更改相对仅限于一组清晰的相关端点,则可以通过为资源引入新名称并相应地更新文档来进行导航。

# variants, which has a breaking change, is accessed on multiple routes
/variants
/products/:id/variants
# we introduce product-variants instead
/product-variants
/products/:id/product-variants

主干(trunk)指的是经常在以下类别中的一个的变化的结果的应用程序级的变化:

  • 格式(例如,从XML到JSON)
  • 规范(例如,从内部到JSON API或Open API)
  • 必需的标题(例如,用于身份验证/授权)

这些将需要更改您的整体API版本,因此您应该仔细计划并正确执行过渡。

一个根本的变化将迫使你走一步,确保您的API的所有版本的消费者都知道这种变化。

API版本控制的类型

当我们转向不同类型的API版本控制时,我们将希望利用这些见解对API更改的不同范围进行评估。每种方法在根据其范围来解决更改时都有其优点和缺点。

有几种方法可以管理您的API版本。URI路径版本控制是最常见的。

URI路径

http://www.example.com/api/v1/products
http://api.example.com/v1/products

此策略涉及将版本号放在URI的路径中,并且通常使用前缀“ v”来完成。API设计人员通常会使用它来引用其应用程序版本(即“ trunk”)而不是终结点版本(即“ leaf”或“ branch”),但这并不总是一个安全的假设。

URI路径版本控制意味着应用程序版本的协调发行,这将需要以下两种方法之一:在开发新版本时维护一个版本,或强迫使用者等待新资源,直到新版本发布为止。这也意味着您需要在版本之间保留所有未更改的端点。但是,对于挥发性相对较低的API,这仍然是一个不错的选择。

您可能不希望将您的版本号与端点或资源的版本号相关联,因为它很容易导致类似于v4of,products但v1of of variants,这会造成混乱。

查询参数(Query Params)

http://www.example.com/api/products?version=1

这种类型的版本控制将查询参数添加到指示版本的请求中。在“叶子”级别上请求资源的版本方面非常灵活,但是它不包含整个API版本的概念,并且使其自身具有上述注释中提到的不同步问题URI路径的端点级别版本控制。

头部(Header)

Accept: version=1.0

标头方法是一种在提供任何给定资源的请求版本时提供更细粒度的方法。

但是,它埋在请求对象中,不像URI路径选项那样透明。仍然很难确定1.0是引用端点的版本还是API本身。

整合类型

这些方法中的每一个似乎都有缺点,要么偏爱“叶子”范围,要么偏爱“树干”范围,但不支持两者。

如果您需要维护整个API版本,并且还提供对多种资源版本的支持,请考虑URI路径和查询参数类型的混合,或者使用更高级的标头方法。

# URI path and query params combo
http://api.example.com/v1/products?version=1
http://api.example.com/v1/products?version=2
# Extended headers, for http://api.example.com/products
Accept: api-version=1; resource-version=1
Accept: api-version=1; resource-version=2

结论

我们在这里介绍了很多内容,现在回顾一下:

  • API版本控制是透明管理API更改的做法。
  • 管理API归结为定义和发展数据合同以及处理重大更改。
  • 在不破坏更改的情况下发展API的最有效方法是遵循有效的API更改管理原则。
  • 对于大多数API,URI路径中的版本控制是最简单的解决方案。
  • 对于更复杂或易失的API,您可以通过采用URI路径和查询参数方法的集成来管理变化范围。

尽管这些原则应为如何有效管理API更改提供明确的指导,但演变API可能是一门艺术,而不是一门科学。创建和维护可靠的API需要思想和远见。

©2020 edoou.com   京ICP备16001874号-3