Salta ai contenuti

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.

Prima di utilizzare questo generatore, assicurati di avere:

  1. Un progetto API TypeScript Smithy (generato con ts#api usando --framework=smithy)
  2. Un progetto ts#rdb
Terminal window
pnpm nx g @aws/nx-plugin:connection
Puoi anche eseguire una prova per vedere quali file verrebbero modificati
Terminal window
pnpm nx g @aws/nx-plugin:connection --dry-run

Seleziona il tuo progetto backend API Smithy come sorgente e il tuo progetto database relazionale come destinazione.

ParametroTipoPredefinitoDescrizione
sourceProject Obbligatoriostring-Il progetto sorgente
targetProject Obbligatoriostring-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 booleantrueSe 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.

Il generatore modifica tre file esistenti nel tuo backend API Smithy:

  • Directorypackages/api/src
    • context.ts proprietà db aggiunta a ServiceContext
    • handler.ts client Prisma creato all’interno di lambdaHandler, passato a serviceHandler.handle
    • local-server.ts client Prisma creato all’interno del gestore delle richieste, passato a serviceHandler.handle

Inoltre, aggiorna il target dev dell’API per avviare automaticamente il database.

Il generatore aggiunge una proprietà db tipizzata a ServiceContext in context.ts:

packages/api/src/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>>;
}

Il client Prisma viene istanziato all’interno di lambdaHandler e passato attraverso il contesto del servizio:

packages/api/src/handler.ts
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);
};

Accedi a db dal contesto nelle tue implementazioni delle operazioni:

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

Eseguire nuovamente il generatore con una destinazione diversa aggiunge il secondo database accanto al primo. Entrambi i client vengono aggiunti a ServiceContext e istanziati in handler.ts:

packages/api/src/context.ts
export interface ServiceContext {
tracer: Tracer;
logger: Logger;
metrics: Metrics;
myDb: Awaited<ReturnType<typeof getMyDb>>;
otherDb: Awaited<ReturnType<typeof getOtherDb>>;
}
packages/api/src/handler.ts
const myDb = await getMyDb();
const otherDb = await getOtherDb();
const httpResponse = await serviceHandler.handle(httpRequest, {
tracer,
logger,
metrics,
myDb,
otherDb,
});

Per consentire alla tua API di connettersi al database in fase di 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 dell’applicazione, 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:

packages/infra/src/stacks/application-stack.ts
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. In fase di runtime, getPrisma() recupera i dettagli di connessione al database da AWS AppConfig, che è un endpoint di servizio AWS pubblico che richiede accesso a Internet in uscita.

Per le connessioni dirette al cluster Aurora dai runtime Lambda Node.js 20 o successivi, caricare il bundle CA di Amazon RDS impostando NODE_EXTRA_CA_CERTS:

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(),
});

Per maggiori dettagli, consultare 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.

Il generatore applica la stessa iniezione del client Prisma all’interno del gestore delle richieste in local-server.ts:

packages/api/src/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);
});
Terminal window
pnpm nx dev <api-project-name>

Questo avvia sia l’API che il database locale. La variabile d’ambiente LOCAL_DEV=true viene impostata automaticamente, quindi il client Prisma si connette al database Docker locale invece che ad Aurora.