发布于 2026-01-06 0 阅读
0

逐步演进您的 RESTful API

逐步演进您的 RESTful API

设计一个直观易用的 RESTful API 并非易事。如果是第一次尝试,那可能已经是一项艰巨的任务。API 的生命周期管理往往被忽略。但即便如此,也并非不可能:在本文中,我想提出一种务实的 API 演进方法,即使最初并未对此进行规划。

初始情况

我们来看一个示例应用程序,它在使用时会显示“Hello”。

> curl http://org. apisix/hello
Hello world

> curl http://org. apisix/hello/Joe
Hello Joe
Enter fullscreen mode Exit fullscreen mode

底层技术并不重要;我们将专注于API部分。

初始情况

使用 API 网关

第一步也是最关键的一步是停止将应用程序直接暴露在互联网上,并在两者之间设置一个 API 网关。如果您不熟悉 API 网关的概念,可以将其理解为功能更强大的反向代理。维基百科给出了如下定义:

网关:一种充当 API 前端的服务器,它接收 API 请求,执行限流和安全策略,将请求传递给后端服务,然后将响应返回给请求者。网关通常包含一个转换引擎,用于动态地协调和修改请求和响应。网关还可以提供诸如收集分析数据和提供缓存等功能。此外,网关还可以提供支持身份验证、授权、安全、审计和合规性的功能。

-- API 管理

本文将使用Apache APISIX ,但您可以随意使用您最熟悉的 APISIX。

如果只暴露网关而不是应用程序,则需要更新DNS记录,使其指向网关而非应用程序,并等待更改在全球范围内生效。这可能需要一些时间。您可以使用dnschecker之类的网站来跟踪更改生效情况

不过,首先需要将 HTTP 请求从网关路由到您的应用程序。
使用 APISIX,您可以通过向网关发送 HTTP 请求来创建路由。

curl http://apisix:9080/apisix/admin/routes/1 -H 'X-API-KEY: xyz' -X PUT -d ' # 1-2
{
  "name": "Direct Route to Old API",               # 3
  "methods": ["GET"],                              # 4
  "uris": ["/hello", "/hello/", "/hello/*"],       # 5
  "upstream": {                                    # 6
    "type": "roundrobin",                          # 8
    "nodes": {
      "oldapi:8081": 1                             # 7
    }
  }
}'
Enter fullscreen mode Exit fullscreen mode
  1. APISIX 可以分配一个自动生成的 ID,也可以使用提供的 ID。在本例中,我们选择后者,将其传递到 URL 中,1并使用PUT动词。
  2. 要更新路由,我们需要传递 API 密钥。
  3. 给路线命名并非必须,但有助于我们更好地理解它的功能。
  4. 用于路由的 HTTP 方法数组
  5. 要路由的 URL 数组
  6. 上游指的是后端应用程序。在我们的例子中,它是 Hello World API。
  7. 节点及其权重的哈希表。权重仅在节点数量较多时才有意义,而在这个简单的场景中,节点数量并不多。
  8. 配置多个节点时要使用的负载均衡算法

使用 API 网关

此时,您可以查询网关并获得与之前相同的结果:

> curl http://org. apisix/hello
Hello world

> curl http://org. apisix/hello/Joe
Hello Joe
Enter fullscreen mode Exit fullscreen mode

API 版本控制

API 的演进意味着在某个阶段需要存在多个 API 版本共存的情况。API 的版本控制有三种选择:

类型 例子
查询参数
curl http://org.apisix/hello?version=1
curl http://org.apisix/hello?version=2
标题
curl -H '版本:1' http://org.apisix/hello
curl -H '版本:2' http://org.apisix/hello
小路
curl http://org.apisix/v1/hello
curl http://org.apisix/v2/hello

关于最佳版本控制方案,已有许多文章进行了探讨。本文将以基于路径的版本控制为例,因为它应用最为广泛。APISIX 也支持其他版本控制方案,您可以根据需要选择使用。

在上一节中,我们创建了一个路由,该路由封装了一个上游路由。APISIX 允许我们创建一个具有专用 ID 的上游路由,以便在多个路由中重复使用它。

curl http://apisix:9080/apisix/admin/upstreams/1 -H 'X-API-KEY: xyz' -X PUT -d ' # 1
{
  "name": "Old API",                                                             # 2
  "type": "roundrobin",
  "nodes": {
    "oldapi:8081": 1
  }
}'
Enter fullscreen mode Exit fullscreen mode
  1. 使用upstreams路径
  2. 新上游的有效载荷

我们还需要重写发送到网关的查询,然后再将其转发到上游。上游知道/hello,但/v1/helloAPISIX 允许通过插件进行此类转换、过滤等操作。让我们创建一个插件配置来重写路径:

curl http://apisix:9080/apisix/admin/plugin_configs/1 -H 'X-API-KEY: xyz' -X PUT -d ' # 1
{
  "plugins": {
    "proxy-rewrite": {                                        # 2
      "regex_uri": ["/v1/(.*)", "/$1"]                        # 3
    }
  }
}'
Enter fullscreen mode Exit fullscreen mode
  1. 使用plugin-configs路径
  2. 使用代理重写插件
  3. 移除版本前缀

现在我们可以创建引用新创建的上游和插件配置的版本化路由:

curl http://apisix:9080/apisix/admin/routes/2 -H 'X-API-KEY: xyz' -X PUT -d '  # 1
{
  "name": "Versioned Route to Old API",
  "methods": ["GET"],
  "uris": ["/v1/hello", "/v1/hello/", "/v1/hello/*"],
  "upstream_id": 1,
  "plugin_config_id": 1
}'
Enter fullscreen mode Exit fullscreen mode
  1. 妈,你看,一条新路线!

API 版本控制

现阶段,我们配置了两条路由,一条是版本化的,另一条是非版本化的:

> curl http://org. apisix/hello
Hello world

> curl http://org. apisix/v1/hello
Hello world
Enter fullscreen mode Exit fullscreen mode

将用户从非版本化路径迁移到版本化路径。

我们已经对 API 进行了版本控制,但我们的用户可能仍在使用旧版的非版本化 API。我们希望他们迁移,但我们不能直接删除旧路由,因为用户对此并不知情。幸运的是,HTTP 301 状态码可以帮上忙:我们可以告知用户资源已从http://org.apisix/hello迁移到http://org.apisix/v1/hello 。这需要在初始路由上配置重定向插件:

curl http://apisix:9080/apisix/admin/routes/1 -H 'X-API-KEY: xyz' -X PATCH -d '
{
  "plugins": {
    "redirect": {
      "uri": "/v1$uri",
      "ret_code": 301
    }
  }
}'
Enter fullscreen mode Exit fullscreen mode

将用户迁移到版本化应用程序

结果很有意思:

>curl http://apisix. org/hello

<html>
<head><title>301 Moved Permanently</title></head>
<body>
<center><h1>301 Moved Permanently</h1></center>
<hr><center>openresty</center>
</body>
</html>

>curl -L apisix:9080/hello                     # 1
Hello world
Enter fullscreen mode Exit fullscreen mode
  1. -L选项遵循重定向规则。

用户要么会因为遵循新端点而透明地使用它,要么他们的集成会中断,他们会注意到 301 状态和要使用的新 API 位置。

了解你的用户

您可能已经注意到,我们之前完全不知道谁在使用我们的API。当我们需要进行更改时,必须想方设法避免影响用户的正常使用。其他一些更改可能就没那么容易应对了。因此,我们应该努力了解用户,以便在必要时与他们取得联系。

坦白说,大多数开发者,包括我自己,如果可以避免,都不喜欢注册并提供联系方式。我想这要归咎于市场团队,他们不了解我们的想法——别给我打电话,我会主动联系你们。然而,在这个特殊情况下,这样做或许会有好处。

“核弹级”方案会完全禁止用户在注册前调用我们的 API。我更倾向于另一种方案:限制未注册用户在一段时间内可以调用的 API 次数。如果达到限制,我们将返回臭名昭著的 429 HTTP 状态码,并发送一条邀请他们注册的消息。

截至撰写本文时,尚无现成的插件能够实现此功能。但我们可以自行编写插件。APISIX 基于 Lua 引擎,所有提供的插件均使用 Lua 编写。此外,您还可以使用 Go、Python、WebAssembly 或任何基于 JVM 的语言编写插件。

为了简化操作,我编写了一个 Lua 插件。由于本文的目的并非讲解 Lua,因此我不会深入探讨。如果您对代码感兴趣,可以在GitHub上找到它。公开发布后,我们还需要完成以下几个步骤:

  1. 配置 APISIX 以使用该目录:

    apisix:
      extra_lua_path: "/opt/apisix/?.lua"      # 1
    
    1. APISIX 可以使用位于该/opt/apisix/文件夹中的任何 Lua 脚本
  2. 加载插件:

    APISIX 可以热重载自身。我们无需重启它(从而避免停机)即可添加其他插件!

    curl http://apisix:9080/apisix/admin/plugins/reload -H 'X-API-KEY: xyz' -X PUT
    
  3. 修改现有插件配置:

    最后,我们需要配置插件本身。由于我们已经创建了一个专门的插件配置文件,因此只需将其更新为新的配置即可:

    curl http://apisix:9080/apisix/admin/plugin_configs/1 -H 'X-API-KEY: xyz' -X PATCH -d '
    {
      "plugins": {
        "proxy-rewrite": {                                # 1
          "regex_uri": ["/v1/(.*)", "/$1"]
        },
        "unauth-limit": {                                 # 2
          "count": 1,                                     # 3
          "time_window": 60,                              # 3
          "key_type": "var",                              # 4
          "key": "consumer_name",                         # 4
          "rejected_code": 429,
          "rejected_msg": "Please register at https://apisix. org/register to get your API token and enjoy unlimited calls"
        }
      }
    }'
    
    1. 很遗憾,我们需要重复使用现有的插件配置。APISIX 团队正在努力修复此问题,以便您能够在不了解现有插件的情况下向配置中添加新插件。
    2. 我们的插件!
    3. 如果用户已通过身份验证,该插件会将每 60 秒的调用次数限制为一次。否则,它不会进行任何限制。
    4. 下一节将对此进行解释。

现在我们可以检查它是否按预期运行:

>curl apisix:9080/v1/hello
Hello world

>curl apisix:9080/v1/hello
{"error_msg":"Please register at https:\/\/apisix. org\/register to get your API token and enjoy unlimited calls"}
Enter fullscreen mode Exit fullscreen mode

确实如此。

创建用户

根据您对未经认证用户访问的限制程度,您应该会开始看到用户访问注册页面。注册涉及诸多方面,例如:

  • 可以根据需要进行自动化验证,或者需要任意数量的手动验证步骤。
  • 免费或付费
  • 可以是像要求提供电子邮件地址这样简单的操作,也可以根据需要收集更多数据而变得复杂。
  • ETC。

这取决于你的具体情况。

关于 APISIX,最终它意味着需要创建一个新的消费者。要创建这样的消费者,我们需要配置一个用于身份验证的插件。有一些现成的身份验证插件可供选择:基本身份验证、API 密钥、JWT、OpenID、LDAP、Keycloak 等。

本文中,key-auth 插件就足够了。让我们配置一个通过 API 密钥进行身份验证的消费者对象:

curl http://apisix:9080/apisix/admin/consumers -H 'X-API-KEY: xyz' -X PUT -d '
{
  "username": "johndoe",                 # 1
  "plugins": {
    "key-auth": {                        # 2
      "key": "mykey"                     # 3
    }
  }
}'
Enter fullscreen mode Exit fullscreen mode
  1. 消费者身份
  2. 使用的插件
  3. 有效令牌是mykey

请注意,默认标头为apikey。可以配置其他标头:请查看 key-auth 插件文档

现在我们可以测试我们的设置,并验证它是否符合我们的要求:

>curl -H 'apikey: mykey' apisix:9080/v1/hello
Hello world

>curl -H 'apikey: mykey' apisix:9080/v1/hello
Hello world
Enter fullscreen mode Exit fullscreen mode

生产环境中的测试

现阶段,我们已准备好向用户发布改进后的 Hello World API。我假设我们的团队已经进行了全面的测试,但新代码始终存在风险。部署一个漏洞百出的现有应用程序新版本可能会对 API 提供商的形象(以及收入!)产生负面影响。

为了最大限度地降低风险,双方一致同意采取的策略是进行“金丝雀发布”:

金丝雀发布是一种降低在生产环境中引入新软件版本风险的技术,它通过在向整个基础设施推出新版本并使其对所有人可用之前,先将更改缓慢地推出给一小部分用户。

-- CanaryRelease

如果出现故障,只会影响一小部分用户,我们可以在不造成太大影响的情况下回滚更改。但是,借助 API 网关,我们可以在金丝雀发布之前增加一个步骤:将生产环境的流量复制到新的 API 端点。虽然网关会丢弃响应,但我们可以在不影响用户的情况下发现其他错误。

APISIX 提供了proxy-mirror插件,可以将生产环境流量复制到其他节点。让我们更新一下插件配置:

curl http://apisix:9080/apisix/admin/plugin_configs/1 -H 'X-API-KEY: xyz' -X PATCH -d '
{
 "plugins": {
    "proxy-rewrite": {
      "regex_uri": ["/v1/(.*)", "/$1"]
    },
    "unauth-limit": {
      "count": 1,
      "time_window": 60,
      "key_type": "var",
      "key": "consumer_name",
      "rejected_code": 429,
      "rejected_msg": "Please register at https://apisix. org/register to get your API token and enjoy unlimited calls"
    },
    "proxy-mirror": {
      "host": "http://new. api:8082"                             # 1
    }
  }
}'
Enter fullscreen mode Exit fullscreen mode
  1. APISIX 也会向此主机发送流量

生产环境测试

我们可以同时监控新旧端点,确保新端点上的错误数量不会超过旧端点。如果出现错误,我们可以修复错误并重新部署,直到达到预期效果。现在,我们可以进行金丝雀发布了。

首先,我们创建一个指向新 API 的上游:

curl http://apisix:9080/apisix/admin/upstreams/2 -H 'X-API-KEY: xyz' -X PUT -d '
{
  "name": "New API",
  "type": "roundrobin",
  "nodes": {
    "newapi:8082": 1
  }
}'
Enter fullscreen mode Exit fullscreen mode

proxy-mirror然后,我们可以用流量分割插件替换原插件

curl http://apisix:9080/apisix/admin/plugin_configs/1 -H 'X-API-KEY: xyz' -X PATCH -d '
{
 "plugins": {
    "proxy-rewrite": {
      "regex_uri": ["/v1/(.*)", "/$1"]
    },
    "unauth-limit": {
      "count": 1,
      "time_window": 60,
      "key_type": "var",
      "key": "consumer_name",
      "rejected_code": 429,
      "rejected_msg": "Please register at https://apisix. org/register to get your API token and enjoy unlimited calls"
    },
    "traffic-split": {
      "rules": [
        {
          "weighted_upstreams": [      # 1
            {
              "upstream_id": 2,
              "weight": 1
            },
            {
              "weight": 1
            }
          ]
        }
      ]
    }
  }
}'
Enter fullscreen mode Exit fullscreen mode
  1. 为了演示目的,将 50% 的流量发送到新的 API。实际应用中,您可能会从更低的比例开始,甚至可能只允许内部用户访问新接口。
curl -L -H 'apikey: mykey' apisix:9080/hello
Hello world

curl -L -H 'apikey: mykey' apisix:9080/hello
Hello world (souped-up version!)
Enter fullscreen mode Exit fullscreen mode

如果一切顺利,我们可以逐步增加发送到新 API 的流量比例,直到达到 100%。现在我们可以取消流量拆分,并将流量从默认端点重定向到 v2 而不是 v1。

已弃用旧版本

大多数用户可能会迁移到新版本以享受其带来的好处,但仍有一部分用户会继续使用 v1 版本。原因可能有很多:时机不对(提示:永远没有合适的时机)、成本太高、缺乏迁移动力等等。但作为 API 提供商,每个已部署的版本都会产生一定的成本。您迟早都需要停用 v1 版本。

REST 并非标准,但IETF制定了相关的规范草案。更多详情请参阅“弃用 HTTP 标头字段”文档。顾名思义,它基于一个特定的 HTTP 响应标头。

借助 API 网关,我们可以配置路由,使其能够传达未来弃用和替换的信息。为此,APISIX 提供了响应重写功能。虽然它可以重写响应的任何部分,但我们将使用它来添加额外的弃用标头:

curl -v http://apisix:9080/apisix/admin/plugin_configs/1 -H 'X-API-KEY: xyz' -X PATCH -d '
{
 "plugins": {
    "proxy-rewrite": {
      "regex_uri": ["/v1/(.*)", "/$1"]
    },
    "unauth-limit": {
      "count": 1,
      "time_window": 60,
      "key_type": "var",
      "key": "consumer_name",
      "rejected_code": 429,
      "rejected_msg": "Please register at https://apisix. org/register to get your API token and enjoy unlimited calls"
    },
    "response-rewrite": {
      "headers": {
        "Deprecation": "true",
        "Link": "<$scheme://apisix:$server_port/v2/hello>; rel=\"successor-version\""
      }
    }
  }
}'
Enter fullscreen mode Exit fullscreen mode
curl -v -H 'apikey: mykey' apisix:9080/v1/hello

< HTTP/1. 1 200 
< Content-Type: text/plain;charset=UTF-8
< Content-Length: 11
< Connection: keep-alive
< Date: Fri, 18 Feb 2022 16:33:30 GMT
< Server: APISIX/2. 12. 0
< Link: <http://apisix:9080/v2/hello>; rel="successor-version"
< Deprecation: true
< 
Hello world
Enter fullscreen mode Exit fullscreen mode

结论

本文介绍了一种管理 API 生命周期的简单分步流程:

  1. 不要直接暴露你的 API;在前端设置一个 API 网关。
  2. 使用路径、查询参数或请求头对现有 API 进行版本控制。
  3. 使用 301 状态码将用户从非版本化端点迁移到版本化端点。
  4. 温和地引导用户注册
  5. 首先在生产环境中进行测试,方法是复制流量,然后将一小部分用户迁移到新版本。
  6. 正式发布新版本
  7. 通过标准响应头通知用户旧版本已弃用

更进一步:

原文发表于A Java Geek 网站,日期为2022 年2 月 27日。

文章来源:https://dev.to/apisix/evolving-your-restful-apis-a-step-by-step-approach-4plo