关系型数据库
此生成器创建一个由 Amazon Aurora(PostgreSQL 或 MySQL)和 Prisma ORM 支持的新关系数据库项目。它生成使用 AWS CDK 或 Terraform 配置和管理数据库所需的应用程序代码和基础设施,具有声明式架构定义、自动迁移部署和类型安全的 ORM 客户端。
生成关系数据库
Section titled “生成关系数据库”您可以通过两种方式生成新的关系数据库项目:
- 安装 Nx Console VSCode Plugin 如果您尚未安装
- 在VSCode中打开Nx控制台
- 点击
Generate (UI)在"Common Nx Commands"部分 - 搜索
@aws/nx-plugin - ts#rdb - 填写必需参数
- 点击
Generate
pnpm nx g @aws/nx-plugin:ts#rdbyarn nx g @aws/nx-plugin:ts#rdbnpx nx g @aws/nx-plugin:ts#rdbbunx nx g @aws/nx-plugin:ts#rdb| 参数 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| name 必需 | string | - | 要生成的数据库项目名称 |
| directory | string | packages | 存储应用程序的目录。 |
| subDirectory | string | - | 项目所在的子目录。默认为项目名称。 |
| service 必需 | string | Aurora | 要配置的关系型数据库服务。 |
| engine 必需 | string | PostgreSQL | 与所选服务一起使用的数据库引擎。 |
| databaseUser | string | dbadmin | 数据库管理员用户名。默认为 'dbadmin'。 |
| databaseName | string | - | 初始数据库名称。默认为项目名称。 |
| ormFramework 必需 | string | Prisma | 用于生成项目的 ORM 框架。 |
| iacProvider | string | Inherit | 首选的 IaC 提供商。默认情况下,这将继承自您的初始选择。 |
生成器将在 <directory>/<name> 目录中创建以下项目结构:
文件夹prisma
文件夹models
- example.prisma 示例模型定义
- schema.prisma 主 Prisma 架构(引用模型)
文件夹scripts
- docker-pull.ts 拉取用于本地开发的数据库 Docker 镜像
- docker-start.ts 启动本地数据库容器
- wait-for-db.ts 等待本地数据库准备就绪
文件夹src
- index.ts 项目入口点
- constants.ts 本地开发连接详细信息和运行时配置键
- prisma.ts Prisma 运行时客户端包装器
- utils.ts 运行时配置和密钥助手
- create-db-user-handler.ts 用于在部署期间创建应用程序数据库用户的 Lambda 处理程序
- migration-handler.ts 用于在部署期间运行数据库迁移的 Lambda 处理程序
- .gitignore Git 忽略条目,包括生成的 Prisma 客户端输出
- Dockerfile 迁移处理程序的容器镜像定义
- project.json 项目配置和构建目标
- prisma.config.ts Prisma CLI 的配置
由于该生成器会根据您选择的 iacProvider 以基础设施即代码的形式输出,它将在 packages/common 目录下创建一个包含相关 CDK 构造体或 Terraform 模块的项目。
通用的基础设施即代码项目结构如下:
文件夹packages/common/constructs
文件夹src
文件夹app/ 针对特定项目/生成器的基础设施构造体
- …
文件夹core/ 被
app目录构造体重用的通用构造体- …
- index.ts 导出
app目录构造体的入口文件
- project.json 项目构建目标与配置
文件夹packages/common/terraform
文件夹src
文件夹app/ 针对特定项目/生成器的 Terraform 模块
- …
文件夹core/ 被
app目录模块重用的通用模块- …
- project.json 项目构建目标与配置
文件夹packages/common/constructs/src
文件夹app
文件夹dbs
- <name>.ts 特定于您的数据库的基础设施
文件夹core
文件夹rdb
- aurora.ts 通用 Aurora 数据库构造
文件夹packages/common/terraform/src
文件夹app
文件夹dbs
文件夹<name>
- <name>.tf 特定于您的数据库的模块
文件夹core
文件夹rdb
文件夹aurora
- aurora.tf 通用 Aurora 模块
生成的项目使用 Prisma ORM 来定义数据库架构并生成类型安全的客户端。工作流程是模型优先的:在数据库项目的 prisma/models/ 目录下添加或更新 Prisma 模型文件,然后从这些模型更改生成迁移。
User 模型示例:
model User { id Int @id @default(autoincrement()) firstName String lastName String}有关更多详细信息,请参阅官方 Prisma 数据建模指南。
生成数据库客户端
Section titled “生成数据库客户端”生成器会自动配置 generate 目标,以便在构建项目时创建类型安全的 TypeScript Prisma 客户端。客户端被写入 generated/prisma(已添加到 .gitignore)。
您也可以随时手动生成客户端:
pnpm nx generate <your-db-project-name>yarn nx generate <your-db-project-name>npx nx generate <your-db-project-name>bunx nx generate <your-db-project-name>使用 prisma 目标从工作区根目录运行 Prisma CLI 命令:
pnpm nx run <project>:prisma generateyarn nx run <project>:prisma generatenpx nx run <project>:prisma generatebunx nx run <project>:prisma generatesrc/prisma.ts 中的运行时包装器导出:
DB_PACKAGE_NAME- 在 AWS AppConfig 的database运行时配置命名空间下使用的键getPrisma()- 从 AWS AppConfig 加载数据库连接设置并使用 IAM 认证创建 Prisma 客户端
客户端自动:
- 使用
RUNTIME_CONFIG_APP_ID环境变量从 AWS AppConfig 检索数据库配置 - 通过 AWS RDS Signer 生成临时认证令牌以进行 IAM 认证
- 通过证书验证管理 SSL/TLS 连接
- 通过持久数据库连接池处理连接池
在 prisma/models/ 下添加或更新模型后,使用 migrate dev 生成迁移文件并同时将它们应用到本地数据库。
生成的 prisma 目标会在运行前通过 Docker 自动启动本地数据库:
pnpm nx run <project>:prisma migrate devyarn nx run <project>:prisma migrate devnpx nx run <project>:prisma migrate devbunx nx run <project>:prisma migrate dev如果您只想生成迁移文件而不将它们应用到本地数据库,请添加 --create-only:
pnpm nx run <project>:prisma migrate dev --create-onlyyarn nx run <project>:prisma migrate dev --create-onlynpx nx run <project>:prisma migrate dev --create-onlybunx nx run <project>:prisma migrate dev --create-only这会在每次架构更改时在 prisma/migrations 中生成一个新的迁移文件夹:
文件夹prisma
文件夹migrations
文件夹20260405013911_initial_migrations
- migration.sql
- migration_lock.toml
- schema.prisma
当您部署 AWS 堆栈时,生成的基础设施会自动将生成的迁移应用到已部署的数据库。
应用现有迁移
Section titled “应用现有迁移”当您拉取其他开发人员创建的迁移文件时,使用 migrate deploy 将这些现有迁移应用到本地数据库。
pnpm nx run <project>:prisma migrate deployyarn nx run <project>:prisma migrate deploynpx nx run <project>:prisma migrate deploybunx nx run <project>:prisma migrate deploy在此本地开发流程中,migrate deploy 将迁移文件应用到本地数据库;它不会将数据库部署到 AWS。
运行 Prisma 命令
Section titled “运行 Prisma 命令”生成的 prisma 目标公开了 Prisma CLI,因此您可以使用它对本地数据库运行 Prisma 支持的任何命令。有关可用命令,请参阅 Prisma CLI 参考。
pnpm nx run <project>:prisma <prisma-command>yarn nx run <project>:prisma <prisma-command>npx nx run <project>:prisma <prisma-command>bunx nx run <project>:prisma <prisma-command>Prisma Studio
Section titled “Prisma Studio”Prisma Studio 是本地数据库的可视化编辑器。使用它浏览表、检查和编辑记录、过滤数据、跟踪关系,并通过内置 SQL 控制台运行原始 SQL。它对于在开发期间验证迁移和填充测试数据很有用。使用以下命令启动它:
pnpm nx run <project>:prisma studioyarn nx run <project>:prisma studionpx nx run <project>:prisma studiobunx nx run <project>:prisma studio从 tRPC API 连接
Section titled “从 tRPC API 连接”虽然本节描述如何从 tRPC API 连接到数据库,但它也可作为在任何其他 TypeScript 项目中使用的参考。
在处理程序中使用 Prisma 客户端
Section titled “在处理程序中使用 Prisma 客户端”从数据库包导入 getPrisma 并在处理程序内调用它以获取类型安全的 Prisma 客户端:
import { getPrisma } from ':my-scope/db';import { publicProcedure } from '../init.js';import { ListUsersOutputSchema } from '../schema/index.js';
export const listUsers = publicProcedure .output(ListUsersOutputSchema) .query(async () => { const prisma = await getPrisma(); return prisma.user.findMany({ orderBy: { id: 'asc' } }); });getPrisma() 返回一个延迟初始化的缓存客户端。在同一 Lambda 执行上下文中的后续调用会重用现有连接池,而不是打开新连接。
Prisma 客户端公开从您的 prisma/models/ 架构派生的完全类型化模型,为您提供从数据库一直到 API 响应的端到端类型安全。
通过中间件注入 Prisma 客户端
Section titled “通过中间件注入 Prisma 客户端”与其在每个过程中调用 getPrisma(),您还可以在中间件中解析一次并将其附加到 tRPC 上下文,以便所有下游过程可以直接访问它。
首先,按照生成的中间件的相同模式在 src/middleware/db.ts 中定义插件:
import { getPrisma } from ':my-scope/db';import { initTRPC } from '@trpc/server';
export interface IDbContext { db: Awaited<ReturnType<typeof getPrisma>>;}
export const createDbPlugin = () => { const t = initTRPC.context<IDbContext>().create(); return t.procedure.use(async (opts) => { const db = await getPrisma(); return opts.next({ ctx: { ...opts.ctx, db, }, }); });};然后在 tRPC 初始化中将其连接到基础过程:
import { createDbPlugin } from './middleware/db.js';
export const dbProcedure = publicProcedure.concat(createDbPlugin());基于 dbProcedure 构建的过程通过上下文接收 db,无需自己导入或调用 getPrisma():
import { dbProcedure } from '../init.js';import { ListUsersOutputSchema } from '../schema/index.js';
export const listUsers = dbProcedure .output(ListUsersOutputSchema) .query(async ({ ctx: { db } }) => { return db.user.findMany({ orderBy: { id: 'asc' } }); });部署您的数据库
Section titled “部署您的数据库”关系数据库生成器根据您选择的 iacProvider 创建 CDK 或 Terraform 基础设施。
CDK 构造在 common/constructs 中创建。使用示例:
import { MyDatabase } from ':my-scope/common-constructs';
export class ApplicationStack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props); ... const db = new MyDatabase(this, 'Db', { vpc, vpcSubnets: { subnetType: SubnetType.PRIVATE_ISOLATED, } }); }}这会配置一个带有 RDS Proxy、管理员凭证、应用程序数据库用户、运行时配置注册和迁移处理程序的 Aurora 集群。
生成的基础设施创建两个数据库用户:
- 管理员用户 - 在集群配置期间创建,凭证存储在 AWS Secrets Manager 中
- 应用程序用户 - 通过 Lambda 自定义资源创建,启用 IAM 认证并对应用程序数据库具有完全权限
Terraform 模块在 common/terraform 中创建。使用示例:
module "my_database" { source = "../../common/terraform/src/app/dbs/my-database"
vpc_id = module.vpc.vpc_id database_subnet_ids = module.vpc.private_isolated_subnet_ids lambda_subnet_ids = module.vpc.private_subnet_ids
tags = local.common_tags}这会配置一个带有 RDS Proxy、管理员凭证、create-db-user Lambda、运行时配置注册、迁移 Lambda 和容器注册表资源的 Aurora 集群。
生成的基础设施创建两个数据库用户:
- 管理员用户 - 在集群配置期间创建,凭证存储在 AWS Secrets Manager 中
- 应用程序用户 - 通过 Lambda 函数创建,启用 IAM 认证并对应用程序数据库具有完全权限
应用程序用户会自动创建一个随机名称并启用 IAM 认证。getPrisma() 已配置为使用短期 RDS 令牌作为此用户进行认证,因此您的应用程序代码永远不会处理数据库密码。
您的 VPC 应包括公共子网、具有出口的私有子网和私有隔离子网。数据库可以在私有隔离子网中运行,而 API Lambda 函数应在具有出口的私有子网中运行,以便它们可以访问 AWS 服务(如 AppConfig)。
const vpc = new Vpc(this, 'Vpc', { subnetConfiguration: [ { name: 'public', subnetType: SubnetType.PUBLIC, }, { name: 'private_with_egress', subnetType: SubnetType.PRIVATE_WITH_EGRESS, }, { name: 'private_isolated', subnetType: SubnetType.PRIVATE_ISOLATED, }, ],});module "vpc" { source = "terraform-aws-modules/vpc/aws" version = "~> 6.0"
name = "app" ... public_subnet_names = ["public"] private_subnet_names = ["private_with_egress"] intra_subnet_names = ["private_isolated"]
enable_nat_gateway = true single_nat_gateway = true}将 API 连接到数据库
Section titled “将 API 连接到数据库”在应用程序堆栈中,将 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 Api(this, 'Api', { integrations: Api.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 服务端点。私有隔离子网中的 Lambda 函数没有出站互联网访问权限,无法访问 AppConfig。具有出口的私有子网通过位于公共子网中的 NAT 网关路由出站流量。
将数据库模块输出传递到计算模块,以便它可以访问数据库并读取其运行时配置:
module "my_database" { source = "../../common/terraform/src/app/dbs/my-database" vpc_id = module.vpc.vpc_id database_subnet_ids = module.vpc.private_isolated_subnet_ids lambda_subnet_ids = module.vpc.private_subnet_ids}
module "api" { source = "..." vpc_id = module.vpc.vpc_id private_subnet_ids = module.vpc.private_subnet_ids
# 允许 API 从 AppConfig 读取运行时配置 appconfig_application_id = module.my_database.appconfig_application_id
# 为应用程序数据库用户授予 rds-db:connect database_cluster_resource_id = module.my_database.cluster_resource_id database_runtime_user = module.my_database.database_runtime_user
# 允许对数据库端口的网络访问 database_security_group_id = module.my_database.security_group_id database_port = module.my_database.cluster_port
environment_variables = { RUNTIME_CONFIG_APP_ID = module.my_database.appconfig_application_id }}将 API Lambda 函数部署到具有出口的私有子网(推荐)或公共子网,而不是私有隔离子网。在运行时,getPrisma() 从 AWS AppConfig 检索数据库连接详细信息,这是一个公共 AWS 服务端点。私有隔离子网中的 Lambda 函数没有出站互联网访问权限,无法访问 AppConfig。具有出口的私有子网通过需要公共子网的 NAT 网关路由出站流量。
确保 API Lambda 角色在 arn:aws:rds-db:<region>:<account>:dbuser:<cluster_resource_id>/<database_runtime_user> 上具有 rds-db:connect 和 AppConfig 读取权限,API 的安全组可以在数据库端口上访问数据库安全组,并且 Lambda 环境包含 RUNTIME_CONFIG_APP_ID。
RDS Proxy 配置
Section titled “RDS Proxy 配置”生成的基础设施默认包含一个 RDS Proxy,它位于您的应用程序和 Aurora 集群之间。RDS Proxy 提供多项好处:
- 连接池 - 维护一个可在应用程序实例间共享的数据库连接池,减少建立新连接的开销
- 连接弹性 - 在 Aurora 实例替换或维护期间自动处理故障转移和重新连接
- IAM 认证 - 支持基于 IAM 的数据库认证,无需在应用程序代码中管理数据库凭证
- 改进的安全性 - 对所有连接强制执行 TLS 加密
禁用 RDS Proxy
Section titled “禁用 RDS Proxy”您可以按如下方式禁用 RDS 代理:
import { MyDatabase } from ':my-scope/common-constructs';
const db = new MyDatabase(this, 'Db', { ... enableRdsProxy: false,});禁用 RDS Proxy 后,您的应用程序将直接连接到 Aurora 集群端点。
对于从 Node.js 20 或更高版本的 Lambda 运行时直接连接 Aurora 集群,请配置 Lambda 函数以加载 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(),});默认情况下,RDS Proxy 已启用。生成的运行时客户端(getPrisma())会自动通过代理端点连接。如果需要,您可以禁用它:
module "my_database" { source = "../../common/terraform/src/app/dbs/my-database" ... enable_rds_proxy = false}禁用 RDS Proxy 后,您的应用程序将直接连接到 Aurora 集群端点。
对于从 Node.js 20 或更高版本的 Lambda 运行时直接连接 Aurora 集群,请配置 Lambda 函数以加载 Amazon RDS CA 捆绑包:
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 捆绑包。
生成的基础设施可以自定义以匹配您的工作负载要求。以下示例演示了一些可用的常见自定义选项。
为您的 Aurora 集群配置写入器和读取器实例。
import { MyDatabase } from ':my-scope/common-constructs';
const db = new MyDatabase(this, 'Db', { ... writer: ClusterInstance.serverlessV2('writer'), readers: [ClusterInstance.serverlessV2('reader')],});module "my_database" { source = "../../common/terraform/src/app/dbs/my-database" ... instance_count = 2 # 1 个写入器 + 1 个读取器}无服务器容量
Section titled “无服务器容量”控制 Aurora Serverless v2 扩展限制以匹配您的工作负载。
import { MyDatabase } from ':my-scope/common-constructs';
const db = new MyDatabase(this, 'Db', { ... serverlessV2MinCapacity: 0.5, serverlessV2MaxCapacity: 8,});module "my_database" { source = "../../common/terraform/src/app/dbs/my-database" ... serverless_min_capacity = 0.5 serverless_max_capacity = 8}固定特定的 Aurora 引擎版本。
默认情况下,生成的本地 Docker 数据库镜像与默认 Aurora 引擎版本匹配。如果您更改 Aurora 引擎版本,建议也使用匹配的本地 Docker 数据库版本以获得最大兼容性。请参阅 AWS 发布说明中的 Aurora PostgreSQL 版本和 Aurora MySQL 版本以确定相应的社区数据库版本。
本地数据库镜像在生成的数据库项目的 project.json 中的 serve-local 目标中配置。当您更改引擎版本时,请更新传递给 scripts/docker-start.ts 的镜像参数。
import { MyDatabase } from ':my-scope/common-constructs';
const db = new MyDatabase(this, 'Db', { ... engineVersion: AuroraPostgresEngineVersion.VER_17_7,});module "my_database" { source = "../../common/terraform/src/app/dbs/my-database" ... engine_version = "17.7"}import { MyDatabase } from ':my-scope/common-constructs';
const db = new MyDatabase(this, 'Db', { ... engineVersion: AuroraMysqlEngineVersion.VER_3_12_0,});module "my_database" { source = "../../common/terraform/src/app/dbs/my-database" ... engine_version = "8.0.mysql_aurora.3.12.0"}默认情况下启用删除保护(CDK 中的 deletionProtection: true,Terraform 中的 deletion_protection = true)以保护 Aurora 集群免遭意外删除。
禁用删除保护
Section titled “禁用删除保护”您可以为预期删除数据库的环境禁用删除保护,例如短期开发或预览堆栈。
import { MyDatabase } from ':my-scope/common-constructs';
const db = new MyDatabase(this, 'Db', { ... deletionProtection: false,});module "my_database" { source = "../../common/terraform/src/app/dbs/my-database" ... deletion_protection = false}CDK 构造默认保留 Aurora 集群(removalPolicy: RemovalPolicy.RETAIN)。当您希望 CDK 堆栈删除时对集群进行快照或销毁时,请更改此设置。
使用 RemovalPolicy.DESTROY 时,还必须禁用删除保护才能删除集群。
import { RemovalPolicy } from 'aws-cdk-lib';import { MyDatabase } from ':my-scope/common-constructs';
const db = new MyDatabase(this, 'Db', { ... removalPolicy: RemovalPolicy.SNAPSHOT,});对于应随堆栈一起删除数据库的临时环境:
import { RemovalPolicy } from 'aws-cdk-lib';import { MyDatabase } from ':my-scope/common-constructs';
const db = new MyDatabase(this, 'Db', { ... deletionProtection: false, removalPolicy: RemovalPolicy.DESTROY,});Terraform 不使用 CDK 移除策略。默认情况下,模块在删除时创建最终快照(skip_final_snapshot = false)。要跳过临时环境的最终快照:
module "my_database" { source = "../../common/terraform/src/app/dbs/my-database" ... deletion_protection = false skip_final_snapshot = true}加密密钥轮换
Section titled “加密密钥轮换”用于加密 Aurora 集群及其凭证密钥的 KMS 密钥默认启用自动密钥轮换。如果您的安全策略在外部管理轮换,请禁用它。
import { MyDatabase } from ':my-scope/common-constructs';
const db = new MyDatabase(this, 'Db', { ... enableKeyRotation: false,});module "my_database" { source = "../../common/terraform/src/app/dbs/my-database" ... enable_key_rotation = false}MySQL:API Gateway 流式模式
Section titled “MySQL:API Gateway 流式模式”当将 Aurora MySQL 与 API Gateway 流式响应(例如使用 tRPC 的 httpBatchStreamLink)一起使用时,Prisma MySQL 客户端在查询完成后会保持 Node.js 事件循环,从而阻止 Lambda 刷新流并结束请求。
要解决此问题,在每次查询后在 finally 块中显式断开客户端连接,以便事件循环可以自由退出并完成流式响应。
选项 1:每个过程
export const listExampleTable = publicProcedure .output(z.array(ExampleTableSchema)) .query(async () => { const prisma = await getPrisma(); try { return await prisma.exampleTable.findMany(); } finally { await prisma.$disconnect(); } });选项 2:tRPC 中间件
如果您使用中间件模式,请将 $disconnect() 调用添加到中间件,以便基于它构建的所有过程都会自动覆盖:
import { getPrisma } from ':my-scope/db';import { initTRPC } from '@trpc/server';
export interface IDbContext { db: Awaited<ReturnType<typeof getPrisma>>;}
export const createDbPlugin = () => { const t = initTRPC.context<IDbContext>().create(); return t.procedure.use(async (opts) => { const db = await getPrisma(); try { return await opts.next({ ctx: { ...opts.ctx, db, }, }); } finally { await db.$disconnect(); } });};MySQL:IAM 令牌过期
Section titled “MySQL:IAM 令牌过期”RDS IAM 认证令牌在 15 分钟后过期。MySQL Prisma 客户端在调用 getPrisma() 时将 IAM 令牌捕获为静态值。现有的打开连接不受影响,但如果在令牌过期后需要建立新连接,认证将失败。PostgreSQL 适配器通过在池每次打开新连接时动态刷新令牌来避免这种情况,但 MySQL 适配器没有等效机制。
对于长时间运行的任务(如批处理作业或数据迁移),在每个工作单元开始时调用 getPrisma(),而不是为整个操作调用一次。因为 getPrisma() 总是为 MySQL 创建一个新客户端并获取新的 IAM 令牌,这确保每个连接都使用有效的令牌进行认证。