在亚马逊 API Gateway 中使用 Apache Velocity 模板语言的最佳实践

作者: 本杰明·史密斯 |

这篇文章由高级解决方案架构师本·弗莱伯格和高级解决方案架构师马库斯·齐勒撰写。

最常见的无服务器模式之一是使用 亚马逊 API Gateway 和 亚马逊云科技 Lambda 构建的 API 。这种方法得到了多种语言的许多不同框架的支持。但是,与 亚马逊云科技 的直接集成可以让客户提高其无服务器架构的成本效益和灵活性。这篇博文讨论了使用 Apache Velocity 模板 在 API Gatewa y 中直接集成服务的最佳实践。

在通过 Velocity 模板和 Lambda 函数进行集成之间做出选择

API 网关中 Velocity 模板的许多用例也可以通过 Lambda 来解决。使用 Lambda,与不同后端集成的复杂性从 Velocity 模板语言 (VTL) 转移到了编程语言。这允许客户使用其首选编程语言生态系统中的现有框架和方法。

但是,许多客户选择在 亚马逊云科技 上使用无服务器来构建精益架构,而使用 Lambda 函数等其他服务可能会增加应用程序的复杂性。客户可以使用不同的注意事项来评估这两种方法之间的利弊。

开发者体验

Apache Velocity 有许多可以在计算表达式时使用的运算符,最突出的是在 #if #set 指令中。这些运算符允许您在 Velocity 模板中实现复杂的转换和业务逻辑。

但是,这增加了开发工作流程的多个方面的复杂性:

  • 测试 :可以测试 Velocity 模板,但工具和方法不如 Lambda 函数中使用的传统编程语言成熟。
  • :API Gateway 为 VTL 提供实用功能,可简化数据转换等常见用例。编程语言库通常提供的其他功能(例如 Python 标准库 )可能在您的模板中不可用。
  • 记录: 无法 从 Velocity 模板将信息记录到 亚马逊 CloudWatch ,因此无法选择保留这些信息。
  • 跟踪: API Gateway 支持通过 亚马逊云科技 X- Ray 进行请求跟踪, 以便与 A mazon DynamoD B 等服务进行原生集成。

您应该使用 VTL 进行数据映射和转换,而不是使用复杂的业务逻辑。也有例外,但将 VTL 用于其他用例的缺点往往大于好处。

API 生命周期

在决定使用 Velocity 还是 Lambda 时,API 生命周期是需要考虑的重要方面。在早期阶段,需求通常没有明确定义,在探索解决方案空间时可能会迅速变化。在与 Amazon Dynamo DB 等数据库集成并找到在持久层上组织数据的最佳方法时,通常会发生这种情况。

对于 DynamoDB 来说,这通常意味着属性、数据类型或主键的更改。在这种情况下,从Lambda开始是明智的决定。使用编程语言编写代码可以为开发人员提供更多的回旋余地和灵活性。这缩短了变更的反馈周期,可以改善开发者体验。

当 API 成熟并在生产环境中运行时,更改的频率通常会降低,稳定性也会提高。此时,评估是否可以通过将逻辑移入 Velocity 模板来取代 Lambda 函数是有意义的。特别是对于繁忙的 API,从长远来看,将 Lambda 逻辑迁移到 Velocity 模板的一次性努力可以获得回报,因为它可以减少调用 Lambda 的成本。

延迟

在 Web 应用程序中,用户感知性能的一个主要因素是加载页面所需的时间。在现代的 单页应用程序 中 ,这通常意味着对后端 API 的多个请求。API Gateway 提供的功能可最大限度地减少 API 层上的调用延迟。使用用于服务集成的 Lambda,会在请求的执行流程中添加一个额外的组件,这不可避免地会带来额外的延迟。

额外延迟的程度取决于工作负载的具体情况,通常低至几毫秒。

以下示例除了使用查询 DynamoDB 的 Node.js Lambda 函数 的基本 CRUD API 的执行环境 冷启动 外,没有测量任何有意义的延迟差异。我在 Go 和 Python 中观察到类似的结果。

并发性和可扩展性

在请求的执行路径中增加一个 Lambda 函数时,API 的并发性和可扩展性会发生变化。这是由于不同的服务配额和不同服务中的一般扩展行为造成的。

对于 API Gateway,当前的默认配额为每秒 10,000 个请求 (RPS),额外的突发容量由令牌存储桶算法提供,使用的最大存储桶容量为 5,000 个请求。 API 网关配额 与区域无关,而 Lambda 的 默认并发限制 取决于区域。

在初始突发之后,您的函数的并发性每分钟可以额外增加 500 个实例。这种情况一直持续到有足够的实例来处理所有请求,或者直到达到并发限制为止。有关此主题的更多详细信息,请参阅 了解 亚马逊云科技 Lambda 的扩展和吞吐量。

如果您的工作负载遇到流量急剧上升,则直接与持久层集成可以在不限制用户请求的情况下更好地处理此类峰值。特别是对于初始突发容量为 1000 或 500 的区域,这有助于避免限制并提供更稳定的用户体验。

最佳实践

整理您的项目以获得工具支持

亚马逊云科技 CloudFormation 模板等基础设施即代码 (IaC) 项目中使用 VTL 时,它必须作为 字符串嵌入到 IaC 文档中。

这种方法有三个主要缺点:

  • 尤其是在多行 Velocity 模板中,这会导致 IaC 定义难以读取或写入。
  • 诸如 IDE 或 Linter 之类的工具不适用于 Velocity 模板的字符串表示形式。
  • 在 IaC 定义之外无法轻松使用这些模板,例如用于本地测试。

每个方面都会影响开发人员的生产力,并使实现更容易出错。

您应该尽可能将 Velocity 模板的定义与 iaC 模板的定义分开。对于 CDK,实现只需要几行代码。

// The following code is licensed under MIT-0 
import { readFileSync } from 'fs';
import * as path from 'path';

const getUserIntegrationWithVTL = new AwsIntegration({
      service: 'dynamodb',
      integrationHttpMethod: HttpMethods.POST,
      action: 'GetItem',
      options: {
        // Omitted for brevity
        requestTemplates: {
          'application/json': readFileSync(path.join('path', 'to', 'vtl', 'request.vm'), 'utf8').toString(),
        },
        integrationResponses: [
          {
            statusCode: '200',
            responseParameters: {
              'method.response.header.access-control-allow-origin': "'*'",
            },
            responseTemplates: {
              'application/json': readFileSync(path.join('path', 'to', 'vtl', 'request.vm'), 'utf8').toString(),
            },
          },
        ],
      },
    });

这种方法的另一个优点是,它迫使你外部化模板中的变量。在 iaC 文档中定义 Velocity 模板时,可以引用同一 iaC 文档中的其他资源,并通过字符串连接在 Velocity 模板中设置该值。但是,这会将值硬编码到模板中,而不是推荐的使用 阶段变 量的方式。

在本地测试 Velocity 模板

客户在使用 Velocity 模板时经常面临的一个挑战是在实施模板时如何缩短反馈循环。测试模板更改的常见工作流程是:

  1. 对模板进行更改。
  2. 部署堆栈。
  3. 测试 API 端点。
  4. 评估结果或检查日志中是否有错误。
  5. 完成或返回步骤 1。

根据堆栈部署的持续时间,这通常会导致几分钟的反馈循环。尽管 Velocity 的测试生态系统远未像主流编程语言那样广泛,但在编写 VTL 时,仍有一些方法可以改善开发者体验。

带有 亚马逊云科技 开发工具包的本地速度渲染引擎

当 API Gateway 收到具有 亚马逊云科技 集成目标的请求时,会发生以下情况:

  1. 检索请求上下文 :API Gateway 检索请求参数和阶段变量。
  2. 发出请求:正文: API Gateway 使用 1 中的模板和变量来呈现 JSON 文档。
  3. 发送请求 :API Gateway 向相应的 亚马逊云科技 服务发出 API 调用。它将请求的授权(通过它的 IAM 角色)、编码和其他方面抽象出来,因此只需 API Gateway 提供请求正文
  4. 检索响应 :API 网关从 API 调用中检索 JSON 响应。
  5. 生成响应正文 :如果调用成功,则使用 JSON 响应作为输入来呈现响应模板。然后,结果将发送回向 API Gateway 发起请求的客户端

为了简化我们的开发工作流程,您可以使用您选择的 亚马逊云科技 开发工具包和 Velocity 渲染引擎在本地复制上述流程。

我推荐使用 Node.js 有两个原因:

  • velocityjs 库是一款轻量级但功能强大的 Velocity 渲染引擎
  • 适用于 JavaScript 的 亚马逊云科技 开发工具包的客户端方法(例如 dynamodbclient.query (jsonBody ))通常期望与 AWS REST API 相同的 JSON 正 文。因此,对于大多数用例,不需要转换(例如驼峰大小写到 Pascal 大小写)

以下代码段显示如何测试 Velocity 模板以获取 DynamoDB 服务集成的请求和响应。它从文件加载模板并使用上下文和参数呈现它们。

// The following code is licensed under MIT-0 
const fs = require('fs')
const Velocity = require('velocityjs');
const AWS = require('@aws-sdk/client-dynamodb');
const ddb = new AWS.DynamoDB()

const requestTemplate = fs.readFileSync('path/to/vtl/request.vm', 'utf8')
const responseTemplate = fs.readFileSync(''path/to/vtl/response.vm', 'utf8')

async function testDynamoDbIntegration() {
  const requestTemplateAsString = Velocity.render(requestTemplate, {
    // Mocks the variables provided by API Gateway
    context: {
      arguments: {
        tableName: 'MyTable'
      }
    },
    input: {
      params: function() {
        return 'someId123'
      },
    },
  });

  print(requestTemplateAsString)

  const sdkJsonRequestBody = JSON.parse(requestTemplateAsString)
  const item = await ddb.query(sdkJsonRequestBody)

  const response = Velocity.render(responseTemplate, {
    input: {
      path: function() {
        return {
          Items: item.Items
        }
      },
    },
  })

  const jsonResponse = JSON.parse(response)
}

这种方法并未涵盖所有用例,最终必须通过部署模板进行验证。但是,它有助于将一个反馈循环的长度从几分钟缩短到几秒钟,并允许在开发 Velocity 模板时加快迭代。

结论

这篇博文讨论了在 API Gateway 中使用 Velocity 模板的注意事项和最佳实践。在 Lambda 和 VTL 之间进行选择时,开发者体验、延迟、API 生命周期、成本和可扩展性是关键因素。对于大多数用例,我们建议将 Lambda 作为起点,将 VTL 作为优化步骤。

为 VTL 设置本地测试环境有助于显著缩短反馈回路并提高开发人员的工作效率。亚马逊云科技 CDK 是使用 VTL 项目时推荐的 IaC 框架,因为它使您能够高效地将基础设施组织为代码项目以提供工具支持。

如需更多无服务器学习资源,请访问 无服务器世界


*前述特定亚马逊云科技生成式人工智能相关的服务仅在亚马逊云科技海外区域可用,亚马逊云科技中国仅为帮助您发展海外业务和/或了解行业前沿技术选择推荐该服务。