Smithy TypeScript API 参考文档
Smithy 是一个协议无关的接口定义语言,用于以模型驱动的方式编写 API。
Smithy TypeScript API 生成器通过 Smithy 进行服务定义,并基于 Smithy TypeScript Server SDK 实现创建新的 API。该生成器提供 CDK 或 Terraform 基础设施即代码(IaC),用于将服务部署到 AWS Lambda 并通过 AWS API Gateway REST API 暴露。它支持基于 Smithy 模型的自动代码生成,实现类型安全的 API 开发。生成的处理器使用 AWS Lambda Powertools for TypeScript 实现可观测性,包括日志记录、AWS X-Ray 追踪和 CloudWatch 指标。
生成 Smithy TypeScript API
Section titled “生成 Smithy TypeScript API”可通过两种方式生成新的 Smithy TypeScript API:
- 安装 Nx Console VSCode Plugin 如果您尚未安装
- 在VSCode中打开Nx控制台
- 点击
Generate (UI)
在"Common Nx Commands"部分 - 搜索
@aws/nx-plugin - ts#smithy-api
- 填写必需参数
- 点击
Generate
pnpm nx g @aws/nx-plugin:ts#smithy-api
yarn nx g @aws/nx-plugin:ts#smithy-api
npx nx g @aws/nx-plugin:ts#smithy-api
bunx nx g @aws/nx-plugin:ts#smithy-api
参数 | 类型 | 默认值 | 描述 |
---|---|---|---|
name 必需 | string | - | The name of the API (required). Used to generate class names and file paths. |
namespace | string | - | The namespace for the Smithy API. Defaults to your monorepo scope |
computeType | string | ServerlessApiGatewayRestApi | The type of compute to use to deploy this API. |
auth | string | IAM | The method used to authenticate with your API. Choose between IAM (default), Cognito or None. |
directory | string | packages | The directory to store the application in. |
iacProvider | string | Inherit | The preferred IaC provider. By default this is inherited from your initial selection. |
生成器在 <directory>/<api-name>
目录下创建两个关联项目:
文件夹model/ Smithy 模型项目
- project.json 项目配置与构建目标
- smithy-build.json Smithy 构建配置
- build.Dockerfile 构建 Smithy 产物的 Docker 配置
文件夹src/
- main.smithy 主服务定义
文件夹operations/
- echo.smithy 示例操作定义
文件夹backend/ TypeScript 后端实现
- project.json 项目配置与构建目标
- rolldown.config.ts 打包配置
文件夹src/
- handler.ts AWS Lambda 处理器
- local-server.ts 本地开发服务器
- service.ts 服务实现
- context.ts 服务上下文定义
文件夹operations/
- echo.ts 示例操作实现
文件夹generated/ 生成的 TypeScript SDK(构建时创建)
- …
由于此生成器根据所选 iacProvider
创建基础设施即代码,因此会在 packages/common
中创建包含相关 CDK 构造或 Terraform 模块的项目。
通用基础设施即代码项目结构如下:
文件夹packages/common/constructs
文件夹src
文件夹app/ 针对特定项目/生成器的基础设施构造
文件夹apis/
- <project-name>.ts 部署 API 的 CDK 构造
文件夹core/ 被
app
中构造复用的通用构造文件夹api/
- rest-api.ts 部署 REST API 的 CDK 构造
- utils.ts API 构造的实用工具
- index.ts 导出
app
构造的入口点
- project.json 项目构建目标与配置
文件夹packages/common/terraform
文件夹src
文件夹app/ 针对特定项目/生成器的 Terraform 模块
文件夹apis/
文件夹<project-name>/
- <project-name>.tf 部署 API 的模块
文件夹core/ 被
app
中模块复用的通用模块文件夹api/
文件夹rest-api/
- rest-api.tf 部署 REST API 的模块
- project.json 项目构建目标与配置
实现 Smithy API
Section titled “实现 Smithy API”在 Smithy 中定义操作
Section titled “在 Smithy 中定义操作”操作在模型项目的 Smithy 文件中定义。主服务定义位于 main.smithy
:
$version: "2.0"
namespace your.namespace
use aws.protocols#restJson1use smithy.framework#ValidationException
@title("YourService")@restJson1service YourService { version: "1.0.0" operations: [ Echo, // 在此添加操作 ] errors: [ ValidationException ]}
独立操作在 operations/
目录的单独文件中定义:
$version: "2.0"
namespace your.namespace
@http(method: "POST", uri: "/echo")operation Echo { input: EchoInput output: EchoOutput}
structure EchoInput { @required message: String
foo: Integer bar: String}
structure EchoOutput { @required message: String}
在 TypeScript 中实现操作
Section titled “在 TypeScript 中实现操作”操作实现位于后端项目的 src/operations/
目录。每个操作使用 TypeScript Server SDK(根据 Smithy 模型在构建时生成)生成的类型实现。
import { ServiceContext } from '../context.js';import { Echo as EchoOperation } from '../generated/ssdk/index.js';
export const Echo: EchoOperation<ServiceContext> = async (input) => { // 业务逻辑在此编写 return { message: `Echo: ${input.message}` // 基于 Smithy 模型的类型安全 };};
操作需在 src/service.ts
中注册到服务定义:
import { ServiceContext } from './context.js';import { YourServiceService } from './generated/ssdk/index.js';import { Echo } from './operations/echo.js';// 导入其他操作
// 在此将操作注册到服务export const Service: YourServiceService<ServiceContext> = { Echo, // 添加其他操作};
可在 context.ts
中定义操作的共享上下文:
export interface ServiceContext { // 默认提供 Powertools 追踪器、日志记录器和指标 tracer: Tracer; logger: Logger; metrics: Metrics; // 添加共享依赖项、数据库连接等 dbClient: any; userIdentity: string;}
此上下文传递给所有操作实现,可用于共享数据库连接、配置或日志工具等资源。
使用 AWS Lambda Powertools 实现可观测性
Section titled “使用 AWS Lambda Powertools 实现可观测性”生成器通过 Middy 中间件配置结构化日志记录,并自动注入上下文。
export const handler = middy<APIGatewayProxyEvent, APIGatewayProxyResult>() .use(captureLambdaHandler(tracer)) .use(injectLambdaContext(logger)) .use(logMetrics(metrics)) .handler(lambdaHandler);
可通过上下文在操作实现中引用日志记录器:
import { ServiceContext } from '../context.js';import { Echo as EchoOperation } from '../generated/ssdk/index.js';
export const Echo: EchoOperation<ServiceContext> = async (input, ctx) => { ctx.logger.info('您的日志消息'); // ...};
通过 captureLambdaHandler
中间件自动配置 AWS X-Ray 追踪。
export const handler = middy<APIGatewayProxyEvent, APIGatewayProxyResult>() .use(captureLambdaHandler(tracer)) .use(injectLambdaContext(logger)) .use(logMetrics(metrics)) .handler(lambdaHandler);
可在操作中添加自定义子段到追踪中:
import { ServiceContext } from '../context.js';import { Echo as EchoOperation } from '../generated/ssdk/index.js';
export const Echo: EchoOperation<ServiceContext> = async (input, ctx) => { // 创建新子段 const subsegment = ctx.tracer.getSegment()?.addNewSubsegment('custom-operation'); try { // 业务逻辑在此编写 } catch (error) { subsegment?.addError(error as Error); throw error; } finally { subsegment?.close(); }};
通过 logMetrics
中间件自动收集每个请求的 CloudWatch 指标。
export const handler = middy<APIGatewayProxyEvent, APIGatewayProxyResult>() .use(captureLambdaHandler(tracer)) .use(injectLambdaContext(logger)) .use(logMetrics(metrics)) .handler(lambdaHandler);
可在操作中添加自定义指标:
import { MetricUnit } from '@aws-lambda-powertools/metrics';import { ServiceContext } from '../context.js';import { Echo as EchoOperation } from '../generated/ssdk/index.js';
export const Echo: EchoOperation<ServiceContext> = async (input, ctx) => { ctx.metrics.addMetric("CustomMetric", MetricUnit.Count, 1); // ...};
Smithy 提供内置错误处理。可在 Smithy 模型中定义自定义错误:
@error("client")@httpError(400)structure InvalidRequestError { @required message: String}
并将其注册到操作/服务:
operation MyOperation { ... errors: [InvalidRequestError]}
然后在 TypeScript 实现中抛出:
import { InvalidRequestError } from '../generated/ssdk/index.js';
export const MyOperation: MyOperationHandler<ServiceContext> = async (input) => { if (!input.requiredField) { throw new InvalidRequestError({ message: "必填字段缺失" }); }
return { /* 成功响应 */ };};
构建与代码生成
Section titled “构建与代码生成”Smithy 模型项目使用 Docker 构建 Smithy 产物并生成 TypeScript Server SDK:
pnpm nx run <model-project>:build
yarn nx run <model-project>:build
npx nx run <model-project>:build
bunx nx run <model-project>:build
此过程:
- 编译 Smithy 模型 并进行验证
- 生成 OpenAPI 规范 从 Smithy 模型
- 创建 TypeScript Server SDK 包含类型安全的操作接口
- 输出构建产物 到
dist/<model-project>/build/
后端项目在编译时自动复制生成的 SDK:
pnpm nx run <backend-project>:copy-ssdk
yarn nx run <backend-project>:copy-ssdk
npx nx run <backend-project>:copy-ssdk
bunx nx run <backend-project>:copy-ssdk
生成器会自动配置一个使用 Rolldown 创建部署包的 bundle
目标:
pnpm nx run <project-name>:bundle
yarn nx run <project-name>:bundle
npx nx run <project-name>:bundle
bunx nx run <project-name>:bundle
Rolldown 配置位于 rolldown.config.ts
文件中,每个要生成的包都有对应的入口配置。如果定义了多个包,Rolldown 会并行管理这些包的创建过程。
生成器配置了支持热重载的本地开发服务器:
pnpm nx run <backend-project>:serve
yarn nx run <backend-project>:serve
npx nx run <backend-project>:serve
bunx nx run <backend-project>:serve
部署 Smithy API
Section titled “部署 Smithy API”生成器根据所选 iacProvider
创建 CDK 或 Terraform 基础设施。
部署 API 的 CDK 构造位于 common/constructs
文件夹:
import { MyApi } from ':my-scope/common-constructs';
export class ExampleStack extends Stack { constructor(scope: Construct, id: string) { // 将 API 添加到堆栈 const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(), }); }}
此配置包括:
- 用于 Smithy 服务的 AWS Lambda 函数
- 作为函数触发器的 API Gateway REST API
- IAM 角色与权限
- CloudWatch 日志组
- X-Ray 追踪配置
部署 API 的 Terraform 模块位于 common/terraform
文件夹:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
# Lambda 函数的环境变量 env = { ENVIRONMENT = var.environment LOG_LEVEL = "INFO" }
# 如需额外 IAM 策略 additional_iam_policy_statements = [ # 添加 API 所需的额外权限 ]
tags = local.common_tags}
此配置包括:
- 托管 Smithy API 的 AWS Lambda 函数
- 作为函数触发器的 API Gateway REST API
- IAM 角色与权限
- CloudWatch 日志组
- X-Ray 追踪配置
- CORS 配置
Terraform 模块提供多个输出:
# 访问 API 端点output "api_url" { value = module.my_api.stage_invoke_url}
# 访问 Lambda 函数详情output "lambda_function_name" { value = module.my_api.lambda_function_name}
REST/HTTP API 的 CDK 构造被配置为提供类型安全接口,用于为每个操作定义集成。
您可以使用静态方法 defaultIntegrations
来应用默认模式,这会为每个操作定义独立的 AWS Lambda 函数:
new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(),});
Terraform 模块自动使用路由模式与单个 Lambda 函数,无需额外配置:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
# 模块自动创建处理所有 API 操作的 # 单个 Lambda 函数 tags = local.common_tags}
您可以通过 API 构造的 integrations
属性以类型安全的方式访问底层 AWS Lambda 函数。例如,如果您的 API 定义了名为 sayHello
的操作,并需要为此函数添加权限:
const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(),});
// sayHello 的类型与 API 中定义的操作相匹配api.integrations.sayHello.handler.addToRolePolicy(new PolicyStatement({ effect: Effect.ALLOW, actions: [...], resources: [...],}));
在 Terraform 的路由模式下,只有一个 Lambda 函数。可通过模块输出来访问:
# 为单个 Lambda 函数授予额外权限resource "aws_iam_role_policy" "additional_permissions" { name = "additional-api-permissions" role = module.my_api.lambda_execution_role_name
policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = [ "s3:GetObject", "s3:PutObject" ] Resource = "arn:aws:s3:::my-bucket/*" } ] })}
自定义默认选项
Section titled “自定义默认选项”如需自定义创建 Lambda 函数时的默认选项,可使用 withDefaultOptions
方法。例如为所有 Lambda 函数配置 VPC:
const vpc = new Vpc(this, 'Vpc', ...);
new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this) .withDefaultOptions({ vpc, }) .build(),});
要自定义 VPC 等配置,需要修改生成的 Terraform 模块。例如为所有 Lambda 函数添加 VPC 支持:
# 添加 VPC 变量variable "vpc_subnet_ids" { description = "Lambda 函数的 VPC 子网 ID 列表" type = list(string) default = []}
variable "vpc_security_group_ids" { description = "Lambda 函数的 VPC 安全组 ID 列表" type = list(string) default = []}
# 更新 Lambda 函数资源resource "aws_lambda_function" "api_lambda" { # ... 现有配置 ...
# 添加 VPC 配置 vpc_config { subnet_ids = var.vpc_subnet_ids security_group_ids = var.vpc_security_group_ids }}
使用带 VPC 配置的模块:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
# VPC 配置 vpc_subnet_ids = [aws_subnet.private_a.id, aws_subnet.private_b.id] vpc_security_group_ids = [aws_security_group.lambda_sg.id]
tags = local.common_tags}
使用 withOverrides
方法可以覆盖特定操作的集成。每个覆盖必须指定类型正确的 integration
属性。例如将 getDocumentation
API 指向外部网站:
new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this) .withOverrides({ getDocumentation: { integration: new HttpIntegration('https://example.com/documentation'), }, }) .build(),});
覆盖后的集成通过 api.integrations.getDocumentation
访问时将不再具有 handler
属性。
您可以为集成添加额外属性以实现类型安全的抽象。例如为 REST API 创建 S3 集成后引用存储桶:
const storageBucket = new Bucket(this, 'Bucket', { ... });
const apiGatewayRole = new Role(this, 'ApiGatewayS3Role', { assumedBy: new ServicePrincipal('apigateway.amazonaws.com'),});
storageBucket.grantRead(apiGatewayRole);
const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this) .withOverrides({ getFile: { bucket: storageBucket, integration: new AwsIntegration({ service: 's3', integrationHttpMethod: 'GET', path: `${storageBucket.bucketName}/{fileName}`, options: { credentialsRole: apiGatewayRole, requestParameters: { 'integration.request.path.fileName': 'method.request.querystring.fileName', }, integrationResponses: [{ statusCode: '200' }], }, }), options: { requestParameters: { 'method.request.querystring.fileName': true, }, methodResponses: [{ statusCode: '200', }], } }, }) .build(),});
// 后续可类型安全地访问我们定义的 bucket 属性api.integrations.getFile.bucket.grantRead(...);
可在集成中提供 options
来覆盖特定方法选项,例如为 getDocumentation
操作使用 Cognito 认证:
new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this) .withOverrides({ getDocumentation: { integration: new HttpIntegration('https://example.com/documentation'), options: { authorizer: new CognitoUserPoolsAuthorizer(...) // REST API 使用,HTTP API 使用 HttpUserPoolAuthorizer } }, }) .build(),});
您可以选择不使用默认集成,直接为每个操作提供集成。这在需要不同集成类型时非常有用:
new MyApi(this, 'MyApi', { integrations: { sayHello: { integration: new LambdaIntegration(...), }, getDocumentation: { integration: new HttpIntegration(...), }, },});
在 Terraform 中实现显式集成,需修改生成的模块:
- 移除默认代理路由(如
resource "aws_apigatewayv2_route" "proxy_routes"
) - 替换单个 Lambda 函数为各操作独立函数
- 为每个操作创建具体集成和路由,复用相同 ZIP 包:
# 移除默认 Lambda 函数和代理集成 resource "aws_lambda_function" "api_lambda" { ... } resource "aws_apigatewayv2_integration" "lambda_integration" { ... } resource "aws_apigatewayv2_route" "proxy_routes" { ... }
# 添加各操作独立 Lambda 函数 resource "aws_lambda_function" "say_hello_handler" { handler = "sayHello.handler" # 特定操作处理器 ... }
resource "aws_lambda_function" "get_documentation_handler" { handler = "getDocumentation.handler" ... }
# 添加具体集成和路由 resource "aws_apigatewayv2_integration" "say_hello_integration" { ... } resource "aws_apigatewayv2_route" "say_hello_route" { ... } resource "aws_apigatewayv2_integration" "get_documentation_integration" { ... } resource "aws_apigatewayv2_route" "get_documentation_route" { ... }
# 添加 Lambda 权限 resource "aws_lambda_permission" "say_hello_permission" { ... } resource "aws_lambda_permission" "get_documentation_permission" { ... }
# 移除默认配置 resource "aws_lambda_function" "api_lambda" { ... } resource "aws_apigatewayv2_integration" "lambda_integration" { ... } resource "aws_apigatewayv2_route" "proxy_routes" { ... }
# 添加各操作资源和集成 resource "aws_api_gateway_resource" "say_hello_resource" { ... } resource "aws_api_gateway_method" "say_hello_method" { ... } resource "aws_api_gateway_integration" "say_hello_integration" { ... }
resource "aws_api_gateway_resource" "get_documentation_resource" { ... } resource "aws_api_gateway_method" "get_documentation_method" { ... } resource "aws_api_gateway_integration" "get_documentation_integration" { ... }
# 更新部署依赖~ resource "aws_api_gateway_deployment" "api_deployment" { depends_on = [ aws_api_gateway_integration.say_hello_integration, aws_api_gateway_integration.get_documentation_integration, ] }
# 添加 Lambda 权限 resource "aws_lambda_permission" "say_hello_permission" { ... } resource "aws_lambda_permission" "get_documentation_permission" { ... }
如需部署单个 Lambda 函数处理所有请求,可修改 defaultIntegrations
方法:
export class MyApi<...> extends ... {
public static defaultIntegrations = (scope: Construct) => { const router = new Function(scope, 'RouterHandler', { ... }); return IntegrationBuilder.rest({ ... buildDefaultIntegration: (op) => { return { // 所有集成引用同一个路由处理器 integration: new LambdaIntegration(router), }; }, }); };}
Terraform 模块默认使用路由模式,自动创建处理所有操作的单个 Lambda 函数:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
tags = local.common_tags}
由于操作在 Smithy 中定义,我们使用代码生成向 CDK 构造提供元数据以实现类型安全集成。
在通用构造的 project.json
中添加 generate:<ApiName>-metadata
目标以促进此代码生成,生成如 packages/common/constructs/src/generated/my-api/metadata.gen.ts
的文件。由于此文件在构建时生成,版本控制中会忽略。
授予访问权限(仅限 IAM)
Section titled “授予访问权限(仅限 IAM)”如果选择 IAM
认证,可使用 grantInvokeAccess
方法授予 API 访问权限:
api.grantInvokeAccess(myIdentityPool.authenticatedRole);
# 创建允许调用 API 的 IAM 策略resource "aws_iam_policy" "api_invoke_policy" { name = "MyApiInvokePolicy" description = "允许调用 Smithy API 的策略"
policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = "execute-api:Invoke" Resource = "${module.my_api.api_execution_arn}/*/*" } ] })}
# 将策略附加到 IAM 角色resource "aws_iam_role_policy_attachment" "api_invoke_access" { role = aws_iam_role.authenticated_user_role.name policy_arn = aws_iam_policy.api_invoke_policy.arn}
调用 Smithy API
Section titled “调用 Smithy API”要从 React 网站调用 API,可使用 api-connection
生成器,该生成器根据 Smithy 模型提供类型安全的客户端生成。