API Smithy a Base de Datos Relacional
El generador connection conecta una API Smithy a un proyecto de Base de Datos Relacional, inyectando un cliente Prisma en el contexto del servicio para que todas las implementaciones de operaciones puedan acceder a la base de datos.
Requisitos Previos
Sección titulada «Requisitos Previos»Antes de usar este generador, asegúrate de tener:
- Un proyecto Smithy TypeScript API (generado con
ts#apiusando--framework=smithy) - Un proyecto
ts#rdb
Ejecutar el Generador
Sección titulada «Ejecutar el Generador»- Instale el Nx Console VSCode Plugin si aún no lo ha hecho
- Abra la consola Nx en VSCode
- Haga clic en
Generate (UI)en la sección "Common Nx Commands" - Busque
@aws/nx-plugin - connection - Complete los parámetros requeridos
- Haga clic en
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:connectionTambién puede realizar una ejecución en seco para ver qué archivos se cambiarían
pnpm nx g @aws/nx-plugin:connection --dry-runyarn nx g @aws/nx-plugin:connection --dry-runnpx nx g @aws/nx-plugin:connection --dry-runbunx nx g @aws/nx-plugin:connection --dry-runSelecciona tu proyecto de backend de API Smithy como origen y tu proyecto de base de datos relacional como destino.
Opciones
Sección titulada «Opciones»| Parámetro | Tipo | Predeterminado | Descripción |
|---|---|---|---|
| sourceProject Requerido | string | - | El proyecto de origen |
| targetProject Requerido | string | - | El proyecto de destino al que conectar |
| sourceComponent | string | - | El componente de origen desde el que conectar (nombre del componente, ruta relativa a la raíz del proyecto de origen, o id del generador). Use '.' para seleccionar explícitamente el proyecto como origen. |
| targetComponent | string | - | El componente de destino al que conectar (nombre del componente, ruta relativa a la raíz del proyecto de destino, o id del generador). Use '.' para seleccionar explícitamente el proyecto como destino. |
| preferInstallDependencies | boolean | true | Si se prefiere instalar las dependencias después de que se ejecute el generador. Establecer en false para diferir la instalación al ejecutar múltiples generadores en lote (la instalación aún se ejecuta si es necesario para que los generadores subsiguientes puedan calcular el grafo de proyectos de Nx); instalar una vez al final. |
Salida del Generador
Sección titulada «Salida del Generador»El generador modifica tres archivos existentes en tu backend de API Smithy:
Directoriopackages/api/src
- context.ts propiedad
dbagregada aServiceContext - handler.ts Cliente Prisma creado dentro de
lambdaHandler, pasado aserviceHandler.handle - local-server.ts Cliente Prisma creado dentro del manejador de solicitudes, pasado a
serviceHandler.handle
- context.ts propiedad
Además, actualiza el target dev de la API para iniciar la base de datos automáticamente.
Cómo Funciona
Sección titulada «Cómo Funciona»ServiceContext
Sección titulada «ServiceContext»El generador agrega una propiedad db tipada a ServiceContext en context.ts:
import { getPrisma as getMyDb } from ':my-scope/my-db';
export interface ServiceContext { tracer: Tracer; logger: Logger; metrics: Metrics; myDb: Awaited<ReturnType<typeof getMyDb>>;}Lambda Handler
Sección titulada «Lambda Handler»El cliente Prisma se instancia dentro de lambdaHandler y se pasa a través del contexto del servicio:
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);};Usar la Base de Datos en las Operaciones
Sección titulada «Usar la Base de Datos en las Operaciones»Accede a db desde el contexto en tus implementaciones de operaciones:
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 };};Múltiples Bases de Datos
Sección titulada «Múltiples Bases de Datos»Ejecutar el generador nuevamente con un destino diferente agrega la segunda base de datos junto a la primera. Ambos clientes se agregan a ServiceContext y se instancian en 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,});Infraestructura
Sección titulada «Infraestructura»Para permitir que tu API se conecte a la base de datos en tiempo de ejecución, las funciones Lambda de la API deben desplegarse en la misma VPC que la base de datos y se les debe otorgar acceso de red e IAM.
En tu stack de aplicación, despliega la API en la misma VPC que la base de datos, luego llama a allowDefaultPortFrom y grantConnect para abrir la ruta de red y otorgar el permiso IAM rds-db:connect a cada manejador Lambda:
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);});Despliega las funciones Lambda de la API en una subred privada con salida, no en una subred privada aislada. En tiempo de ejecución, getPrisma() recupera los detalles de conexión de la base de datos desde AWS AppConfig, que es un endpoint de servicio público de AWS que requiere acceso a internet saliente.
Despliega la API en la misma VPC que la base de datos, otórgale rds-db:connect a través de additional_iam_policy_statements, y abre la ruta de red con un par de reglas de grupo de seguridad. Los recursos aws_vpc.main y aws_subnet están definidos en la guía de despliegue de la base de datos:
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}" ] }, { Effect = "Allow" Action = [ "appconfig:StartConfigurationSession", "appconfig:GetLatestConfiguration" ] Resource = [ "${module.runtime_config_appconfig.application_arn}/*" ] } ]
env = { RUNTIME_CONFIG_APP_ID = module.runtime_config_appconfig.application_id }}
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"}Despliega las funciones Lambda de la API en subredes privadas con salida, no en subredes privadas aisladas. appconfig_application_id/appconfig_application_arn provienen de la aplicación AppConfig de configuración en tiempo de ejecución compartida declarada una vez en tu módulo raíz, no del módulo de base de datos — pasarlos establece RUNTIME_CONFIG_APP_ID en las funciones Lambda y les otorga acceso de lectura a la aplicación. Incluye el namespace database al instanciarlo para que se despliegue la entrada de configuración en tiempo de ejecución del módulo de base de datos:
module "runtime_config_appconfig" { source = "../../common/terraform/src/core/runtime-config/appconfig"
application_name = "my-app-runtime-config" namespaces = ["connection", "agentcore", "database"]}Requisitos de SSL al Conectar sin RDS Proxy
Sección titulada «Requisitos de SSL al Conectar sin RDS Proxy»Para conexiones directas al clúster Aurora desde entornos de ejecución Lambda de Node.js 20 o posterior, cargue el paquete de CA de Amazon RDS configurando NODE_EXTRA_CA_CERTS:
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" }}Para más detalles, consulte los requisitos SSL/TLS para conexiones de Amazon RDS de AWS Lambda y la documentación TLS de Amazon RDS Proxy. Al usar RDS Proxy, no necesita configurar el paquete de CA de RDS en su función Lambda.
Desarrollo Local
Sección titulada «Desarrollo Local»El generador aplica la misma inyección de cliente Prisma dentro del manejador de solicitudes en local-server.ts:
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>Esto inicia tanto la API como la base de datos local. La variable de entorno LOCAL_DEV=true se establece automáticamente, por lo que el cliente Prisma se conecta a la base de datos Docker local en lugar de Aurora.