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
ts#smithy-api(backend TypeScript) - 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. |
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 serve-local 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.
Pasa las salidas del módulo de base de datos a tu módulo de API para que pueda alcanzar la base de datos y leer su configuración en tiempo de ejecución:
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
appconfig_application_id = module.my_database.appconfig_application_id 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 }}Despliega las funciones Lambda de la API en subredes privadas con salida, no en subredes privadas aisladas. Asegúrate de que el rol Lambda de la API tenga el permiso rds-db:connect y que su grupo de seguridad pueda alcanzar el grupo de seguridad de la base de datos en el puerto de la base de datos.
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 serve-local <api-project-name>yarn nx serve-local <api-project-name>npx nx serve-local <api-project-name>bunx nx serve-local <api-project-name>Esto inicia tanto la API como la base de datos local. La variable de entorno SERVE_LOCAL=true se establece automáticamente, por lo que el cliente Prisma se conecta a la base de datos Docker local en lugar de Aurora.