初探 Lambda Powertools TypeScript

2025-06-04

初探 Lambda Powertools TypeScript

AWS 高级解决方案架构师Sara Gerion于 2022 年 1 月 5 日宣布,Lambda Powertools TypeScript进入公测阶段。Lambda Powertools 是一个由 AWS 赞助的开源项目,旨在提升开发人员使用 AWS Lambda 时的体验并促进最佳实践的采用。Lambda Powertools TypeScript 现已加入JavaPython Lambda Powertools 库。

目录

Lambda 的 Node.js 工具

我是 TypeScript 的忠实粉丝,事实上我还合著了一本关于它的书。我不太使用 Java 或 Python,所以虽然我一直对 Lambda Powertools 感兴趣,但直到现在才开始尝试。Lambda Powertools TypeScript 加入了MiddyDAZN Lambda Powertools 的阵营,成为 Node.js 运行时的 Lambda 工具库。Lambda Powertools TypeScript 与同类库的两个区别在于它由 AWS 赞助,并且支持装饰器。

Lambda Powertools TypeScript 支持 AWS SDK for JavaScript 的 v2 和 v3,并为这两个版本提供了示例。

装饰器

关于装饰器,人们的看法不一,但我认为它们是非常有用的抽象,并且在面向对象的 TypeScript 中大量使用它们。然而,TypeScript 有一个相当大的限制,那就是我们可以将装饰器放在类方法上,但不能放在函数上。这意味着,很遗憾,目前我们无法实现这样的代码。

// This doesn't work :(
@doSomethingGreat()
export const handler = async () => {
  ...
};
Enter fullscreen mode Exit fullscreen mode

这很遗憾,因为这本来会是极好的开发者体验。为了将doSomethingGreat装饰器附加到我们的处理程序,我们需要编写类似这样的代码。

class Lambda {
  @doSomethingGreat()
  public async handler() {
    ...
  }
}

const handlerClass = new Lambda();
export const handler = handlerClass.handler;
Enter fullscreen mode Exit fullscreen mode

这只是多出来的五行代码——也许没什么大不了的。无论如何,Powertools 团队能做的不多,因为装饰器可能还需要几年时间才能支持函数。记住,如果我们选择在 Lambda 中使用装饰器和类,我们需要小心this 引用

Lambda 不支持开箱即用的装饰器和 TypeScript(除非使用deno),因此如果我们选择这种方式,还需要一个转译步骤。幸运的是,对于AWS CDKAWS SAM无服务器框架用户来说,这个问题基本上已经解决了。如果您想要或需要自己动手,esbuild是一个很好的起点,并且似乎是实现此目的的首选打包工具。

没有装饰器

我们可能希望不使用类或转译器。Lambda Powertools TypeScript 可以在没有装饰器的情况下使用,实际上也不需要 TypeScript。我们可以将该库与原生 Node.js 一起使用。

Lambda Powertools TypeScript 的文档非常好,并提供了几个示例,GitHub 上还有更多示例

功能

Lambda Powertools 的三个版本均包含 Metrics、Logger 和 Tracer 作为核心实用程序。Lambda Powertools Python包含一个事件处理程序和其他几个支持批处理、幂等性、验证等功能的实用实用程序。

Lambda Powertools 的每个版本都是独立开发的,其功能根据不同运行时的不同需求进行定制。这与 AWS CDK 使用jsii将相同构造发布到多个运行时的方式截然不同。虽然等待某些功能的发布可能令人沮丧,但这很可能是正确的方法,因为考虑将通用代码编译成可以修饰多个运行时支持的自定义代码的代码只会增加复杂性。

为了测试这一点,我在我的一个示例项目中实现了所有三个 Lambda Powertools TypeScript 实用程序。我选择我的CDK 异步测试项目,因为它包含多个 Lambda 函数,并包含通过 EventBridge 和 Step Functions 实现的异步工作流。

如果您想查看我的检测代码,可以在这个分支中找到。

追踪者

为了用 Tracer 来检测我的函数,我需要将它们重写为类。我选择使用装饰器,因为我实在拿不定主意要不要在所有地方都使用类,所以我需要先熟悉一下。首先collect.ts,这是该函数最初的版本,只有 11 行代码。

import { Payment } from '../models/payment';

export const handler = async (input: {
  Payload: { Payment: Payment };
}): Promise<{ Status: number; Payment: Payment }> => {
  const min = 0;
  const max = 1;
  const Status = Math.floor(Math.random() * (max - min + 1)) + min;

  return { Status, Payment: input.Payload.Payment };
};
Enter fullscreen mode Exit fullscreen mode

这是包含 Tracer 的重构。

import { Tracer } from '@aws-lambda-powertools/tracer';

import type { LambdaInterface } from '@aws-lambda-powertools/commons';
import type { Context } from 'aws-lambda';

import type { Payment } from '../models/payment';

const tracer = new Tracer({ serviceName: 'paymentCollections' });

class Lambda implements LambdaInterface {
  @tracer.captureLambdaHandler()
  public async handler(
    input: { Payload: { Payment: Payment } },
    _context: Context,
  ): Promise<{ Status: number; Payment: Payment }> {
    const min = 0;
    const max = 1;
    const Status = Math.floor(Math.random() * (max - min + 1)) + min;

    return { Status, Payment: input.Payload.Payment };
  }
}

const handlerClass = new Lambda();
export const handler = handlerClass.handler;
Enter fullscreen mode Exit fullscreen mode

该函数现在有 25 行代码,这并不可怕。添加 Metrics 和 Logger 实用程序后,最终只有 33 行。当然,由于现在功能更多,我的函数会更大,这是意料之中的。我们应该将这种增长视为加法,而不是乘法。我的 11 行函数变成了 25 行。如果它最初是 111 行,那么它就会增长到 125 行,而不是翻倍。

那么我们能得到什么呢?Tracer 模块包装了AWS X-Ray SDK(作为传递依赖)。它实际上并没有添加任何新功能,但使 SDK 更易于使用。根据我的经验,该 SDK 有点难以理解,所以这样做或许是值得的。我们可以修饰类方法,用一行代码引入新的跟踪片段。我们也可以使用命令式形式在合适的位置添加新的跟踪。我们可以捕获 AWS 客户端,但这只会暴露 X-Ray SDK。

这里(目前?)尚未解决的一个问题是使用 X-Ray SDK 和 DynamoDB DocumentClient 时遇到的奇怪问题。当将 DocumentClient 与 X-Ray 一起使用时,我们需要采取一种变通方法,因为 SDK 需要访问 DocumentClient 服务属性,但该属性并未在类型中公开。以下是我过去解决这个问题的方法。

import DynamoDB, { DocumentClient } from 'aws-sdk/clients/dynamodb';
import { captureAWSClient } from 'aws-xray-sdk-core';

const client = new DocumentClient();
captureAWSClient((client as DocumentClient & { service: DynamoDB }).service);
Enter fullscreen mode Exit fullscreen mode

更新!这个问题在0.5.0 版本中已经解决了!上面的代码现在可以写成:

import { DocumentClient } from 'aws-sdk/clients/dynamodb';
import { captureAWSClient } from 'aws-xray-sdk-core';

const client = new DocumentClient();
captureAWSClient(client);
Enter fullscreen mode Exit fullscreen mode

非常感谢你们快速完成了这项改进!现在我的 DX 性能提升了,以下是我已安装仪表盘的应用的样子。

原文
现在有了 Lambda Powertools TypeScript。
import DynamoDB, { DocumentClient } from 'aws-sdk/clients/dynamodb';
import { Tracer } from '@aws-lambda-powertools/tracer';

const tracer = new Tracer({ serviceName: 'paymentCollections' });
const client = new DocumentClient();
tracer.captureAWSClient((client as DocumentClient & { service: DynamoDB }).service);
Enter fullscreen mode Exit fullscreen mode

所以基本上是一样的。相比于仅仅使用 X-Ray SDK,这个实用程序的优势只有在我们向代码中添加多个段时才会真正显现出来。我不认为我的示例应用程序中会解锁这个功能,但我确实在所有函数和状态机中启用了跟踪,结果相当不错。


ServiceLens 地图

我不仅获得了这张出色的服务地图,还获得了详细的追踪。

追踪 #1

踪迹 #2

Tracer 模块正在添加## index.handler这些屏幕截图中显示的部分。为了充分利用这个工具,我想添加更多跟踪信息。总的来说,通过所有函数和服务获取整个应用程序的详细跟踪信息非常令人印象深刻且实用。X-Ray 完成了大部分工作,但更好的开发者体验意味着我们最终将有更多应用程序得到正确的检测,这当然是一件好事。

这里稍微有点超前了,但跟踪还包括日志,并且已检测段的日志将在 CloudWatch 中的跟踪旁边找到。

跟踪日志

再说一次,这太棒了。我能够以分布式的方式编写我的应用程序,使用单一用途的函数,但在执行时却能看到全局。

如果我们记得在高吞吐量应用中设置采样率,X-Ray 可能是一项相当便宜的服务。定价似乎是基于轨迹的,因此在轨迹中添加额外的片段不会增加成本。

记录器

Logger 实用程序可以替代任何日志记录器,包括控制台日志记录器。Logger 的附加功能是能够将 Lambda 上下文注入到每条日志消息中。当我们使用 注释我们的处理程序方法@logger.injectLambdaContext(),然后使用 logger.info 时,我们将看到如下所示的日志消息。

{
  "cold_start": true,
  "function_arn": "arn:aws:lambda:us-east-1:1234567890:function:openCollection",
  "function_memory_size": 128,
  "function_name": "openCollection",
  "function_request_id": "df21844d-f4b6-49b4-b246-da54183ce5cf",
  "level": "INFO",
  "message": "Payment set to collection",
  "service": "paymentCollections",
  "timestamp": "2022-01-09T18:46:20.622Z"
}
Enter fullscreen mode Exit fullscreen mode

如果我们计划将日志提取到搜索索引中,或者即使我们只想使用 CloudWatch Logs Insights,这也会非常方便,因为其结构可以帮助我们搜索和筛选日志消息。另一方面,如果我们只是浏览几条日志消息,这可能会有点杂乱。我们应该记住,任何日志服务(包括 CloudWatch)都会按量计费,而极其冗长的日志可能会非常昂贵。

考虑到这一点,Logger 实用程序有很多不错的功能,我们可以按照自己的喜好构建日志。此外,Logger 还包含一个采样率功能,可以用来降低成本。

默认情况下,记录器方法接受一个或多个参数。第一个参数必须是带有消息键的字符串或对象。我注意到,如果我将字符串作为后续参数,该字符串将被转换为字符数组并打印出来,所以这一点需要注意。

指标

Metrics 实用程序用于发布自定义 CloudWatch 指标。虽然 Lambda 会自动发布许多有用的指标,例如延迟、并发执行、限流等,但自定义指标让我们有机会通过向指标中添加相关业务事件来完善可观测性。

跟踪可靠性很重要,但它并不能说明全部情况!实际上,自定义指标才是最重要的。本周有多少客户注册?其中有多少人完成了有价值的工作流程?这些问题的答案就在我们的代码中,如果我们发布自定义指标,它们也可以在我们的仪表板中。

自定义指标的定价结构可能比较昂贵。嵌入式指标格式可以帮助管理成本,并且 Lambda Powertools TypeScript 支持该格式。同样,这里的文档已经很完善了,所以我就不多做解释了。我们来看看使用体验。我在 collectionSuccess 函数中添加了一个自定义指标“collectionSuccess”。在我的假设应用中,一些付款最终会被收款,在这里我会标记该收款是否产生了付款。

import { Logger } from '@aws-lambda-powertools/logger';
import { Metrics, MetricUnits } from '@aws-lambda-powertools/metrics';
import { Tracer } from '@aws-lambda-powertools/tracer';

import { PaymentEntity, PaymentStatus } from '../models/payment';

import type { LambdaInterface } from '@aws-lambda-powertools/commons';
import type { Context } from 'aws-lambda';
import type { Payment } from '../models/payment';

const powerToolsConfig = { namespace: 'payments', serviceName: 'paymentCollections' };
const logger = new Logger(powerToolsConfig);
const metrics = new Metrics(powerToolsConfig);
const tracer = new Tracer(powerToolsConfig);

class Lambda implements LambdaInterface {
  @logger.injectLambdaContext()
  @metrics.logMetrics({ captureColdStartMetric: true })
  @tracer.captureLambdaHandler()
  public async handler(input: { Payload: { Payment: Payment } }, _context: Context): Promise<void> {
    try {
      await PaymentEntity.update({ id: input.Payload.Payment.id, status: PaymentStatus.COLLECTION_SUCCESS });
      metrics.addMetric('collectionSuccess', MetricUnits.Count, 1);
    } catch (e) {
      logger.error('Failed to record collection success', e);
      throw e;
    }
  }
}

const handlerClass = new Lambda();
export const handler = handlerClass.handler;
Enter fullscreen mode Exit fullscreen mode

添加后,@metrics.logMetrics我们发出的任何指标也会被记录到 CloudWatch 中。我们可能希望这样,也可能不希望这样(再次考虑成本)。要添加自定义指标,我们只需使用metrics.addMetric

我对应用进行了插桩,使其能够发出冷启动指标以及一些描述应用中重要事件(例如成功和失败的付款和收款)的自定义指标。由于我的应用旨在演示集成测试,因此我还插桩了指示测试运行时间的自定义指标。

CloudWatch 指标

这些指标可以在 CloudWatch Metrics 中找到,放置在仪表板中或通过 API 导出到第三方工具。

包装尺寸

将所有实用程序添加到我的项目中,未压缩时似乎增加了约 600kb,压缩后的文件包增加了约 200kb。考虑到其价值以及将一些依赖项链接到 AWS SDK 或 X-Ray SDK 的必要性,这似乎相当合理,而且团队很好地遵循了“保持精简”的原则。

结论

Lambda Powertools 出色地专注于开发者真正需要的实用工具,帮助他们改进应用程序并遵循最佳实践。其核心模块均注重可观察性,这种重视是必要的,也值得赞赏。团队出色地开发了一套 API,无论开发者是否使用装饰器,都能从中受益。

我热切地期待这个图书馆的全面开放,并将遵循路线图来了解即将发生的事情以及我如何参与其中。

覆盖

文章来源:https://dev.to/aws-builders/first-look-at-lambda-powertools-typescript-2k3p
PREV
每个无服务器开发人员必读的五本书
NEXT
使用 AWS Serverless 创建简单的 OTP 系统