Smithy API 连接到关系数据库
connection 生成器将 Smithy API 连接到 关系数据库 项目,将 Prisma 客户端注入到服务上下文中,以便所有操作实现都可以访问数据库。
在使用此生成器之前,请确保您拥有:
- 一个 Smithy TypeScript API 项目(使用
ts#api和--framework=smithy生成) - 一个
ts#rdb项目
- 安装 Nx Console VSCode Plugin 如果您尚未安装
- 在VSCode中打开Nx控制台
- 点击
Generate (UI)在"Common Nx Commands"部分 - 搜索
@aws/nx-plugin - connection - 填写必需参数
- 点击
Generate
pnpm nx g @aws/nx-plugin:connectionyarn nx g @aws/nx-plugin:connectionnpx nx g @aws/nx-plugin:connectionbunx nx g @aws/nx-plugin:connection选择您的 Smithy API 后端项目作为源,选择您的关系数据库项目作为目标。
| 参数 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| sourceProject 必需 | string | - | 源项目 |
| targetProject 必需 | string | - | 要连接到的目标项目 |
| sourceComponent | string | - | 要从其连接的源组件(组件名称、相对于源项目根目录的路径或生成器 ID)。使用 '.' 显式选择项目作为源。 |
| targetComponent | string | - | 要连接到的目标组件(组件名称、相对于目标项目根目录的路径或生成器 ID)。使用 '.' 显式选择项目作为目标。 |
| preferInstallDependencies | boolean | true | 是否在生成器运行后优先安装依赖项。设置为 false 可在批量运行多个生成器时延迟安装(如果后续生成器需要计算 Nx 项目图,仍会运行安装);在最后统一安装一次。 |
生成器会修改 Smithy API 后端中的三个现有文件:
文件夹packages/api/src
- context.ts 将
db属性添加到ServiceContext - handler.ts 在
lambdaHandler内创建 Prisma 客户端,传递给serviceHandler.handle - local-server.ts 在请求处理程序内创建 Prisma 客户端,传递给
serviceHandler.handle
- context.ts 将
此外,它还会更新 API 的 dev 目标以自动启动数据库。
ServiceContext
Section titled “ServiceContext”生成器在 context.ts 中向 ServiceContext 添加一个类型化的 db 属性:
import { getPrisma as getMyDb } from ':my-scope/my-db';
export interface ServiceContext { tracer: Tracer; logger: Logger; metrics: Metrics; myDb: Awaited<ReturnType<typeof getMyDb>>;}Lambda 处理程序
Section titled “Lambda 处理程序”Prisma 客户端在 lambdaHandler 内实例化,并通过服务上下文传递:
import { getPrisma as getMyDb } from ':my-scope/my-db';
export const lambdaHandler = async (event: APIGatewayProxyEvent) => { const httpRequest = convertEvent(event); const myDb = await getMyDb(); const httpResponse = await serviceHandler.handle(httpRequest, { tracer, logger, metrics, myDb, }); return convertVersion1Response(httpResponse);};在操作中使用数据库
Section titled “在操作中使用数据库”在操作实现中从上下文访问 db:
import { ListUsersOperationInput, ListUsersOperationOutput } from '../generated/ssdk/index.js';import { ServiceContext } from '../context.js';
export const listUsers = async ( input: ListUsersOperationInput, ctx: ServiceContext,): Promise<ListUsersOperationOutput> => { const users = await ctx.myDb.user.findMany(); return { users };};使用不同的目标再次运行生成器会将第二个数据库添加到第一个数据库旁边。两个客户端都会添加到 ServiceContext 并在 handler.ts 中实例化:
export interface ServiceContext { tracer: Tracer; logger: Logger; metrics: Metrics; myDb: Awaited<ReturnType<typeof getMyDb>>; otherDb: Awaited<ReturnType<typeof getOtherDb>>;}const myDb = await getMyDb();const otherDb = await getOtherDb();const httpResponse = await serviceHandler.handle(httpRequest, { tracer, logger, metrics, myDb, otherDb,});为了让您的 API 在运行时能够连接到数据库,API Lambda 函数必须部署到与数据库相同的 VPC 中,并被授予网络和 IAM 访问权限。
在您的应用程序堆栈中,将 API 部署到与数据库相同的 VPC 中,然后调用 allowDefaultPortFrom 和 grantConnect 来打开网络路径并为每个 Lambda 处理程序授予 IAM rds-db:connect 权限:
import { MyDatabase } from ':my-scope/common-constructs';
const db = new MyDatabase(this, 'Db', { vpc, ... });
const api = new MyApi(this, 'Api', { integrations: MyApi.defaultIntegrations(this) .withDefaultOptions({ vpc, vpcSubnets: { subnetType: SubnetType.PRIVATE_WITH_EGRESS }, }) .build(),});
Object.entries(api.integrations).forEach(([operation, integration]) => { db.allowDefaultPortFrom(integration.handler, `Allow ${operation} to connect to the database`); db.grantConnect(integration.handler);});将 API Lambda 函数部署到具有出口的私有子网中,而不是私有隔离子网。在运行时,getPrisma() 从 AWS AppConfig 检索数据库连接详细信息,这是一个需要出站互联网访问的公共 AWS 服务端点。
将 API 部署到与数据库相同的 VPC 中,通过 additional_iam_policy_statements 授予它 rds-db:connect 权限,并使用一对安全组规则打开网络路径。aws_vpc.main 和 aws_subnet 资源在数据库部署指南中定义:
module "my_database" { source = "../../common/terraform/src/app/dbs/my-database" vpc_id = aws_vpc.main.id database_subnet_ids = aws_subnet.database[*].id lambda_subnet_ids = aws_subnet.private[*].id}
module "api" { source = "../../common/terraform/src/app/apis/my-api" enable_vpc = true vpc_id = aws_vpc.main.id subnet_ids = aws_subnet.private[*].id
additional_iam_policy_statements = [ { Effect = "Allow" Action = ["rds-db:connect"] Resource = [ "arn:aws:rds-db:${data.aws_region.current.region}:${data.aws_caller_identity.current.account_id}:dbuser:${module.my_database.connect_resource_id}/${module.my_database.database_runtime_user}" ] } ]}
resource "aws_vpc_security_group_ingress_rule" "api_to_database" { description = "Allow the API Lambda functions to connect to the database" security_group_id = module.my_database.security_group_id referenced_security_group_id = module.api.security_group_id from_port = module.my_database.cluster_port to_port = module.my_database.cluster_port ip_protocol = "tcp"}
resource "aws_vpc_security_group_egress_rule" "api_to_database" { description = "Allow outbound traffic from the API Lambda functions to the database" security_group_id = module.api.security_group_id referenced_security_group_id = module.my_database.security_group_id from_port = module.my_database.cluster_port to_port = module.my_database.cluster_port ip_protocol = "tcp"}将 API Lambda 函数部署到具有出口的私有子网中,而不是私有隔离子网。appconfig_application_id/appconfig_application_arn 来自在根模块中声明一次的共享 运行时配置 AppConfig 应用程序,而不是来自数据库模块——传递它们会在 Lambda 函数上设置 RUNTIME_CONFIG_APP_ID 并授予它们对应用程序的读取访问权限。在实例化时包含 database 命名空间,以便部署数据库模块的运行时配置条目:
module "runtime_config_appconfig" { source = "../../common/terraform/src/core/runtime-config/appconfig"
application_name = "my-app-runtime-config" namespaces = ["connection", "agentcore", "database"]}在不使用 RDS Proxy 连接时的 SSL 要求
Section titled “在不使用 RDS Proxy 连接时的 SSL 要求”对于从 Node.js 20 或更高版本的 Lambda 运行时直接连接 Aurora 集群,需要通过设置 NODE_EXTRA_CA_CERTS 来加载 Amazon RDS CA 证书包:
const api = new Api(this, 'Api', { integrations: Api.defaultIntegrations(this) .withDefaultOptions({ environment: { NODE_EXTRA_CA_CERTS: '/var/runtime/ca-cert.pem', }, }) .build(),});module "api" { source = "..." ...
environment_variables = { NODE_EXTRA_CA_CERTS = "/var/runtime/ca-cert.pem" }}有关更多详细信息,请参阅 AWS Lambda Amazon RDS 连接的 SSL/TLS 要求和 Amazon RDS Proxy TLS 文档。使用 RDS Proxy 时,您无需在 Lambda 函数中配置 RDS CA 证书包。
生成器在 local-server.ts 的请求处理程序内应用相同的 Prisma 客户端注入:
import { getPrisma as getMyDb } from ':my-scope/my-db';
const server = createServer(async function (req, res) { const httpRequest = convertRequest(req); const myDb = await getMyDb(); const httpResponse = await serviceHandler.handle(httpRequest, { tracer, logger, metrics, myDb, }); return writeResponse(httpResponse, res);});pnpm nx dev <api-project-name>yarn nx dev <api-project-name>npx nx dev <api-project-name>bunx nx dev <api-project-name>这将同时启动 API 和本地数据库。LOCAL_DEV=true 环境变量会自动设置,因此 Prisma 客户端会连接到本地 Docker 数据库而不是 Aurora。