API Smithy para Banco de Dados Relacional
O gerador connection conecta uma API Smithy a um projeto de Banco de Dados Relacional, injetando um cliente Prisma no contexto do serviço para que todas as implementações de operações possam acessar o banco de dados.
Pré-requisitos
Seção intitulada “Pré-requisitos”Antes de usar este gerador, certifique-se de ter:
- Um projeto
ts#smithy-api(backend TypeScript) - Um projeto
ts#rdb
Executar o Gerador
Seção intitulada “Executar o Gerador”- Instale o Nx Console VSCode Plugin se ainda não o fez
- Abra o console Nx no VSCode
- Clique em
Generate (UI)na seção "Common Nx Commands" - Procure por
@aws/nx-plugin - connection - Preencha os parâmetros obrigatórios
- Clique em
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:connectionVocê também pode realizar uma execução simulada para ver quais arquivos seriam alterados
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-runSelecione seu projeto de backend da API Smithy como origem e seu projeto de banco de dados relacional como destino.
| Parâmetro | Tipo | Padrão | Descrição |
|---|---|---|---|
| sourceProject Obrigatório | string | - | O projeto de origem |
| targetProject Obrigatório | string | - | O projeto de destino para conectar |
| sourceComponent | string | - | O componente de origem para conectar (nome do componente, caminho relativo à raiz do projeto de origem, ou id do gerador). Use '.' para selecionar explicitamente o projeto como origem. |
| targetComponent | string | - | O componente de destino para conectar (nome do componente, caminho relativo à raiz do projeto de destino, ou id do gerador). Use '.' para selecionar explicitamente o projeto como destino. |
Saída do Gerador
Seção intitulada “Saída do Gerador”O gerador modifica três arquivos existentes no backend da sua API Smithy:
Directorypackages/api/src
- context.ts propriedade
dbadicionada aoServiceContext - handler.ts cliente Prisma criado dentro de
lambdaHandler, passado paraserviceHandler.handle - local-server.ts cliente Prisma criado dentro do manipulador de requisição, passado para
serviceHandler.handle
- context.ts propriedade
Além disso, ele atualiza o target serve-local da API para iniciar o banco de dados automaticamente.
Como Funciona
Seção intitulada “Como Funciona”ServiceContext
Seção intitulada “ServiceContext”O gerador adiciona uma propriedade db tipada ao ServiceContext em 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
Seção intitulada “Lambda Handler”O cliente Prisma é instanciado dentro de lambdaHandler e passado através do contexto do serviço:
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);};Usando o Banco de Dados nas Operações
Seção intitulada “Usando o Banco de Dados nas Operações”Acesse db do contexto nas implementações de suas operações:
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últiplos Bancos de Dados
Seção intitulada “Múltiplos Bancos de Dados”Executar o gerador novamente com um destino diferente adiciona o segundo banco de dados junto com o primeiro. Ambos os clientes são adicionados ao ServiceContext e instanciados em 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,});Infraestrutura
Seção intitulada “Infraestrutura”Para permitir que sua API se conecte ao banco de dados em tempo de execução, as funções Lambda da API devem ser implantadas na mesma VPC que o banco de dados e receber acesso de rede e IAM.
No stack da sua aplicação, implante a API na mesma VPC que o banco de dados, depois chame allowDefaultPortFrom e grantConnect para abrir o caminho de rede e conceder permissão IAM rds-db:connect a cada handler 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);});Implante as funções Lambda da API em uma sub-rede privada com egress, não em uma sub-rede privada isolada. Em tempo de execução, getPrisma() recupera os detalhes de conexão do banco de dados do AWS AppConfig, que é um endpoint de serviço público da AWS que requer acesso à internet de saída.
Passe as saídas do módulo do banco de dados para o módulo da sua API para que ele possa alcançar o banco de dados e ler sua configuração de tempo de execução:
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 }}Implante as funções Lambda da API em sub-redes privadas com egress, não em sub-redes privadas isoladas. Certifique-se de que a role Lambda da API tenha permissão rds-db:connect e que seu grupo de segurança possa alcançar o grupo de segurança do banco de dados na porta do banco de dados.
Desenvolvimento Local
Seção intitulada “Desenvolvimento Local”O gerador aplica a mesma injeção de cliente Prisma dentro do manipulador de requisição em 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>Isso inicia tanto a API quanto o banco de dados local. A variável de ambiente SERVE_LOCAL=true é definida automaticamente, para que o cliente Prisma se conecte ao banco de dados Docker local em vez do Aurora.