跳转到内容

关系型数据库

Filter this guide Pick generator option values to hide sections that don't apply.

此生成器创建一个由 Amazon Aurora(PostgreSQL 或 MySQL)和 Prisma ORM 支持的新关系数据库项目。它生成使用 AWS CDK 或 Terraform 配置和管理数据库所需的应用程序代码和基础设施,具有声明式架构定义、自动迁移部署和类型安全的 ORM 客户端。

您可以通过两种方式生成新的关系数据库项目:

  1. 安装 Nx Console VSCode Plugin 如果您尚未安装
  2. 在VSCode中打开Nx控制台
  3. 点击 Generate (UI) 在"Common Nx Commands"部分
  4. 搜索 @aws/nx-plugin - ts#rdb
  5. 填写必需参数
    • 点击 Generate
    参数 类型 默认值 描述
    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/constructs/src
      • 文件夹app
        • 文件夹dbs
          • <name>.ts 特定于您的数据库的基础设施
      • 文件夹core
        • 文件夹rdb
          • aurora.ts 通用 Aurora 数据库构造

    生成的项目使用 Prisma ORM 来定义数据库架构并生成类型安全的客户端。工作流程是模型优先的:在数据库项目的 prisma/models/ 目录下添加或更新 Prisma 模型文件,然后从这些模型更改生成迁移。

    User 模型示例:

    packages/postgres/prisma/models/user.prisma
    model User {
    id Int @id @default(autoincrement())
    firstName String
    lastName String
    }

    有关更多详细信息,请参阅官方 Prisma 数据建模指南

    生成器会自动配置 generate 目标,以便在构建项目时创建类型安全的 TypeScript Prisma 客户端。客户端被写入 generated/prisma(已添加到 .gitignore)。

    您也可以随时手动生成客户端:

    Terminal window
    pnpm nx generate <your-db-project-name>

    使用 prisma 目标从工作区根目录运行 Prisma CLI 命令:

    Terminal window
    pnpm nx run <project>:prisma generate

    src/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 自动启动本地数据库:

    Terminal window
    pnpm nx run <project>:prisma migrate dev

    如果您只想生成迁移文件而不将它们应用到本地数据库,请添加 --create-only

    Terminal window
    pnpm nx run <project>:prisma migrate dev --create-only

    这会在每次架构更改时在 prisma/migrations 中生成一个新的迁移文件夹:

    • 文件夹prisma
      • 文件夹migrations
        • 文件夹20260405013911_initial_migrations
          • migration.sql
        • migration_lock.toml
      • schema.prisma

    当您部署 AWS 堆栈时,生成的基础设施会自动将生成的迁移应用到已部署的数据库。

    当您拉取其他开发人员创建的迁移文件时,使用 migrate deploy 将这些现有迁移应用到本地数据库。

    Terminal window
    pnpm nx run <project>:prisma migrate deploy

    在此本地开发流程中,migrate deploy 将迁移文件应用到本地数据库;它不会将数据库部署到 AWS。

    生成的 prisma 目标公开了 Prisma CLI,因此您可以使用它对本地数据库运行 Prisma 支持的任何命令。有关可用命令,请参阅 Prisma CLI 参考

    Terminal window
    pnpm nx run <project>:prisma <prisma-command>

    Prisma Studio 是本地数据库的可视化编辑器。使用它浏览表、检查和编辑记录、过滤数据、跟踪关系,并通过内置 SQL 控制台运行原始 SQL。它对于在开发期间验证迁移和填充测试数据很有用。使用以下命令启动它:

    Terminal window
    pnpm nx run <project>:prisma studio

    虽然本节描述如何从 tRPC API 连接到数据库,但它也可作为在任何其他 TypeScript 项目中使用的参考。

    从数据库包导入 getPrisma 并在处理程序内调用它以获取类型安全的 Prisma 客户端:

    packages/api/src/procedures/list-users.ts
    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 响应的端到端类型安全。

    与其在每个过程中调用 getPrisma(),您还可以在中间件中解析一次并将其附加到 tRPC 上下文,以便所有下游过程可以直接访问它。

    首先,按照生成的中间件的相同模式在 src/middleware/db.ts 中定义插件:

    packages/api/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 初始化中将其连接到基础过程:

    packages/api/src/init.ts
    import { createDbPlugin } from './middleware/db.js';
    export const dbProcedure = publicProcedure.concat(createDbPlugin());

    基于 dbProcedure 构建的过程通过上下文接收 db,无需自己导入或调用 getPrisma()

    packages/api/src/procedures/list-users.ts
    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' } });
    });

    关系数据库生成器根据您选择的 iacProvider 创建 CDK 或 Terraform 基础设施。

    CDK 构造在 common/constructs 中创建。使用示例:

    packages/infra/src/stacks/application-stack.ts
    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 认证并对应用程序数据库具有完全权限

    应用程序用户会自动创建一个随机名称并启用 IAM 认证。getPrisma() 已配置为使用短期 RDS 令牌作为此用户进行认证,因此您的应用程序代码永远不会处理数据库密码。

    您的 VPC 应包括公共子网、具有出口的私有子网和私有隔离子网。数据库可以在私有隔离子网中运行,而 API Lambda 函数应在具有出口的私有子网中运行,以便它们可以访问 AWS 服务(如 AppConfig)。

    packages/infra/src/stacks/application-stack.ts
    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,
    },
    ],
    });

    在应用程序堆栈中,将 API 部署到与数据库相同的 VPC 中,然后调用 allowDefaultPortFromgrantConnect 以打开网络路径并向每个 Lambda 处理程序授予 IAM rds-db:connect 权限:

    packages/infra/src/stacks/application-stack.ts
    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 网关路由出站流量。

    生成的基础设施默认包含一个 RDS Proxy,它位于您的应用程序和 Aurora 集群之间。RDS Proxy 提供多项好处:

    • 连接池 - 维护一个可在应用程序实例间共享的数据库连接池,减少建立新连接的开销
    • 连接弹性 - 在 Aurora 实例替换或维护期间自动处理故障转移和重新连接
    • IAM 认证 - 支持基于 IAM 的数据库认证,无需在应用程序代码中管理数据库凭证
    • 改进的安全性 - 对所有连接强制执行 TLS 加密

    您可以按如下方式禁用 RDS 代理:

    packages/infra/src/stacks/application-stack.ts
    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 捆绑包:

    packages/infra/src/stacks/application-stack.ts
    const api = new Api(this, 'Api', {
    integrations: Api.defaultIntegrations(this)
    .withDefaultOptions({
    environment: {
    NODE_EXTRA_CA_CERTS: '/var/runtime/ca-cert.pem',
    },
    })
    .build(),
    });

    有关更多详细信息,请参阅 AWS Lambda Amazon RDS 连接的 SSL/TLS 要求和 Amazon RDS Proxy TLS 文档。使用 RDS Proxy 时,您无需在 Lambda 函数中配置 RDS CA 捆绑包。

    生成的基础设施可以自定义以匹配您的工作负载要求。以下示例演示了一些可用的常见自定义选项。

    为您的 Aurora 集群配置写入器和读取器实例。

    packages/infra/src/stacks/application-stack.ts
    import { MyDatabase } from ':my-scope/common-constructs';
    const db = new MyDatabase(this, 'Db', {
    ...
    writer: ClusterInstance.serverlessV2('writer'),
    readers: [ClusterInstance.serverlessV2('reader')],
    });

    控制 Aurora Serverless v2 扩展限制以匹配您的工作负载。

    packages/infra/src/stacks/application-stack.ts
    import { MyDatabase } from ':my-scope/common-constructs';
    const db = new MyDatabase(this, 'Db', {
    ...
    serverlessV2MinCapacity: 0.5,
    serverlessV2MaxCapacity: 8,
    });

    固定特定的 Aurora 引擎版本。

    默认情况下,生成的本地 Docker 数据库镜像与默认 Aurora 引擎版本匹配。如果您更改 Aurora 引擎版本,建议也使用匹配的本地 Docker 数据库版本以获得最大兼容性。请参阅 AWS 发布说明中的 Aurora PostgreSQL 版本Aurora MySQL 版本以确定相应的社区数据库版本。

    本地数据库镜像在生成的数据库项目的 project.json 中的 serve-local 目标中配置。当您更改引擎版本时,请更新传递给 scripts/docker-start.ts 的镜像参数。

    engine = PostgreSQL
    packages/infra/src/stacks/application-stack.ts
    import { MyDatabase } from ':my-scope/common-constructs';
    const db = new MyDatabase(this, 'Db', {
    ...
    engineVersion: AuroraPostgresEngineVersion.VER_17_7,
    });
    engine = MySQL
    packages/infra/src/stacks/application-stack.ts
    import { MyDatabase } from ':my-scope/common-constructs';
    const db = new MyDatabase(this, 'Db', {
    ...
    engineVersion: AuroraMysqlEngineVersion.VER_3_12_0,
    });

    默认情况下启用删除保护(CDK 中的 deletionProtection: true,Terraform 中的 deletion_protection = true)以保护 Aurora 集群免遭意外删除。

    您可以为预期删除数据库的环境禁用删除保护,例如短期开发或预览堆栈。

    packages/infra/src/stacks/application-stack.ts
    import { MyDatabase } from ':my-scope/common-constructs';
    const db = new MyDatabase(this, 'Db', {
    ...
    deletionProtection: false,
    });

    CDK 构造默认保留 Aurora 集群(removalPolicy: RemovalPolicy.RETAIN)。当您希望 CDK 堆栈删除时对集群进行快照或销毁时,请更改此设置。

    使用 RemovalPolicy.DESTROY 时,还必须禁用删除保护才能删除集群。

    packages/infra/src/stacks/application-stack.ts
    import { RemovalPolicy } from 'aws-cdk-lib';
    import { MyDatabase } from ':my-scope/common-constructs';
    const db = new MyDatabase(this, 'Db', {
    ...
    removalPolicy: RemovalPolicy.SNAPSHOT,
    });

    对于应随堆栈一起删除数据库的临时环境:

    packages/infra/src/stacks/application-stack.ts
    import { RemovalPolicy } from 'aws-cdk-lib';
    import { MyDatabase } from ':my-scope/common-constructs';
    const db = new MyDatabase(this, 'Db', {
    ...
    deletionProtection: false,
    removalPolicy: RemovalPolicy.DESTROY,
    });

    用于加密 Aurora 集群及其凭证密钥的 KMS 密钥默认启用自动密钥轮换。如果您的安全策略在外部管理轮换,请禁用它。

    packages/infra/src/stacks/application-stack.ts
    import { MyDatabase } from ':my-scope/common-constructs';
    const db = new MyDatabase(this, 'Db', {
    ...
    enableKeyRotation: false,
    });
    engine = MySQL

    当将 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() 调用添加到中间件,以便基于它构建的所有过程都会自动覆盖:

    packages/api/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();
    try {
    return await opts.next({
    ctx: {
    ...opts.ctx,
    db,
    },
    });
    } finally {
    await db.$disconnect();
    }
    });
    };

    RDS IAM 认证令牌在 15 分钟后过期。MySQL Prisma 客户端在调用 getPrisma() 时将 IAM 令牌捕获为静态值。现有的打开连接不受影响,但如果在令牌过期后需要建立新连接,认证将失败。PostgreSQL 适配器通过在池每次打开新连接时动态刷新令牌来避免这种情况,但 MySQL 适配器没有等效机制。

    对于长时间运行的任务(如批处理作业或数据迁移),在每个工作单元开始时调用 getPrisma(),而不是为整个操作调用一次。因为 getPrisma() 总是为 MySQL 创建一个新客户端并获取新的 IAM 令牌,这确保每个连接都使用有效的令牌进行认证。