API Smithy a Database Relazionale
Il generatore connection collega un’API Smithy a un progetto Database Relazionale, iniettando un client Prisma nel contesto del servizio in modo che tutte le implementazioni delle operazioni possano accedere al database.
Prerequisiti
Sezione intitolata “Prerequisiti”Prima di utilizzare questo generatore, assicurati di avere:
- Un progetto Smithy TypeScript API (generato con
ts#apiusando--framework=smithy) - Un progetto
ts#rdb
Utilizzo
Sezione intitolata “Utilizzo”Esegui il Generatore
Sezione intitolata “Esegui il Generatore”- Installa il Nx Console VSCode Plugin se non l'hai già fatto
- Apri la console Nx in VSCode
- Clicca su
Generate (UI)nella sezione "Common Nx Commands" - Cerca
@aws/nx-plugin - connection - Compila i parametri richiesti
- Clicca su
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:connectionPuoi anche eseguire una prova per vedere quali file verrebbero modificati
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-runSeleziona il tuo progetto backend API Smithy come sorgente e il tuo progetto database relazionale come destinazione.
Opzioni
Sezione intitolata “Opzioni”| Parametro | Tipo | Predefinito | Descrizione |
|---|---|---|---|
| sourceProject Obbligatorio | string | - | Il progetto sorgente |
| targetProject Obbligatorio | string | - | Il progetto di destinazione a cui connettersi |
| sourceComponent | string | - | Il componente sorgente da cui connettersi (nome del componente, percorso relativo alla radice del progetto sorgente, o id del generatore). Usare '.' per selezionare esplicitamente il progetto come sorgente. |
| targetComponent | string | - | Il componente di destinazione a cui connettersi (nome del componente, percorso relativo alla radice del progetto di destinazione, o id del generatore). Usare '.' per selezionare esplicitamente il progetto come destinazione. |
| preferInstallDependencies | boolean | true | Se preferire l'installazione delle dipendenze dopo l'esecuzione del generatore. Impostare su false per rimandare l'installazione quando si eseguono più generatori in batch (l'installazione viene comunque eseguita se necessaria affinché i generatori successivi possano calcolare il grafo dei progetti Nx); installare una volta alla fine. |
Output del Generatore
Sezione intitolata “Output del Generatore”Il generatore modifica tre file esistenti nel tuo backend API Smithy:
Directorypackages/api/src
- context.ts proprietà
dbaggiunta aServiceContext - handler.ts client Prisma creato all’interno di
lambdaHandler, passato aserviceHandler.handle - local-server.ts client Prisma creato all’interno del gestore delle richieste, passato a
serviceHandler.handle
- context.ts proprietà
Inoltre, aggiorna il target dev dell’API per avviare automaticamente il database.
Come Funziona
Sezione intitolata “Come Funziona”ServiceContext
Sezione intitolata “ServiceContext”Il generatore aggiunge una proprietà db tipizzata a ServiceContext in 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
Sezione intitolata “Lambda Handler”Il client Prisma viene istanziato all’interno di lambdaHandler e passato attraverso il contesto del servizio:
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);};Utilizzo del Database nelle Operazioni
Sezione intitolata “Utilizzo del Database nelle Operazioni”Accedi a db dal contesto nelle tue implementazioni delle operazioni:
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 };};Database Multipli
Sezione intitolata “Database Multipli”Eseguendo nuovamente il generatore con una destinazione diversa, il secondo database viene aggiunto accanto al primo. Entrambi i client vengono aggiunti a ServiceContext e istanziati in 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,});Infrastruttura
Sezione intitolata “Infrastruttura”Per consentire alla tua API di connettersi al database a runtime, le funzioni Lambda dell’API devono essere distribuite nello stesso VPC del database e devono avere accesso di rete e IAM.
Nel tuo stack applicativo, distribuisci l’API nello stesso VPC del database, quindi chiama allowDefaultPortFrom e grantConnect per aprire il percorso di rete e concedere il permesso IAM rds-db:connect a ciascun 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);});Distribuisci le funzioni Lambda dell’API in una subnet privata con egress, non in una subnet privata isolata. A runtime, getPrisma() recupera i dettagli di connessione al database da AWS AppConfig, che è un endpoint pubblico del servizio AWS che richiede accesso internet in uscita.
Distribuisci l’API nello stesso VPC del database, concedigli rds-db:connect tramite additional_iam_policy_statements, e apri il percorso di rete con una coppia di regole di security group. Le risorse aws_vpc.main e aws_subnet sono definite nella guida di distribuzione del database:
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"}Distribuisci le funzioni Lambda dell’API in subnet private con egress, non in subnet private isolate. appconfig_application_id/appconfig_application_arn provengono dall’applicazione AppConfig di configurazione runtime condivisa dichiarata una volta nel tuo modulo root, non dal modulo database — passarli imposta RUNTIME_CONFIG_APP_ID sulle funzioni Lambda e concede loro accesso in lettura all’applicazione. Includi il namespace database quando lo istanzi in modo che venga distribuita la voce di configurazione runtime del modulo database:
module "runtime_config_appconfig" { source = "../../common/terraform/src/core/runtime-config/appconfig"
application_name = "my-app-runtime-config" namespaces = ["connection", "agentcore", "database"]}Requisiti SSL per la Connessione Senza RDS Proxy
Sezione intitolata “Requisiti SSL per la Connessione Senza RDS Proxy”Per le connessioni dirette al cluster Aurora dai runtime Lambda Node.js 20 o successivi, carica il bundle CA di Amazon RDS impostando 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" }}Per maggiori dettagli, consulta i requisiti SSL/TLS per le connessioni Amazon RDS di AWS Lambda e la documentazione TLS di Amazon RDS Proxy. Quando si utilizza RDS Proxy, non è necessario configurare il bundle CA di RDS nella funzione Lambda.
Sviluppo Locale
Sezione intitolata “Sviluppo Locale”Il generatore applica la stessa iniezione del client Prisma all’interno del gestore delle richieste in 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>Questo avvia sia l’API che il database locale. La variabile d’ambiente LOCAL_DEV=true viene impostata automaticamente, in modo che il client Prisma si connetta al database Docker locale invece di Aurora.