Salta ai contenuti

Database Relazionale

Filter this guide Pick generator option values to hide sections that don't apply.

Questo generatore crea un nuovo progetto di database relazionale basato su Amazon Aurora (PostgreSQL o MySQL) e Prisma ORM. Genera il codice applicativo e l’infrastruttura necessaria per effettuare il provisioning e gestire un database utilizzando AWS CDK o Terraform, con definizione dello schema dichiarativa, deployment automatico delle migrazioni e un client ORM type-safe.

Puoi generare un nuovo progetto di database relazionale in due modi:

  1. Installa il Nx Console VSCode Plugin se non l'hai già fatto
  2. Apri la console Nx in VSCode
  3. Clicca su Generate (UI) nella sezione "Common Nx Commands"
  4. Cerca @aws/nx-plugin - ts#rdb
  5. Compila i parametri richiesti
    • Clicca su Generate
    Parametro Tipo Predefinito Descrizione
    name Obbligatorio string - Nome del progetto database da generare
    directory string packages La directory in cui memorizzare l'applicazione.
    subDirectory string - La sottodirectory in cui viene posizionato il progetto. Per impostazione predefinita corrisponde al nome del progetto.
    service Obbligatorio string Aurora Servizio di database relazionale da provisioning.
    engine Obbligatorio string PostgreSQL Motore di database da utilizzare con il servizio selezionato.
    databaseUser string dbadmin Nome utente amministratore del database. Il valore predefinito è 'dbadmin'.
    databaseName string - Nome iniziale del database. Il valore predefinito corrisponde al nome del progetto.
    ormFramework Obbligatorio string Prisma Framework ORM da utilizzare per il progetto generato.
    iacProvider string Inherit Il provider IaC preferito. Per impostazione predefinita viene ereditato dalla selezione iniziale.

    Il generatore creerà la seguente struttura di progetto nella directory <directory>/<name>:

    • Directoryprisma
      • Directorymodels
        • example.prisma Definizione del modello di esempio
      • schema.prisma Schema Prisma principale (fa riferimento ai modelli)
    • Directoryscripts
      • docker-pull.ts Scarica l’immagine Docker del database per lo sviluppo locale
      • docker-start.ts Avvia un container database locale
      • wait-for-db.ts Attende che il database locale sia pronto
    • Directorysrc
      • index.ts Punto di ingresso del progetto
      • constants.ts Dettagli di connessione per lo sviluppo locale e chiave di configurazione runtime
      • prisma.ts Wrapper del client Prisma runtime
      • utils.ts Helper per configurazione runtime e secret
      • create-db-user-handler.ts Gestore Lambda utilizzato per creare l’utente database applicativo durante il deployment
      • migration-handler.ts Gestore Lambda utilizzato per eseguire le migrazioni del database durante il deployment
    • .gitignore Voci di ignore Git incluso l’output del client Prisma generato
    • Dockerfile Definizione dell’immagine container per il gestore delle migrazioni
    • project.json Configurazione del progetto e target di build
    • prisma.config.ts Configurazione per la CLI di Prisma

    Poiché questo generatore fornisce infrastruttura come codice basata sul tuo iacProvider selezionato, creerà un progetto in packages/common che include i relativi costrutti CDK o moduli Terraform.

    Il progetto comune di infrastruttura come codice è strutturato come segue:

    • Directorypackages/common/constructs
      • Directorysrc
        • Directoryapp/ Construct per l’infrastruttura specifica di un progetto/generatore
        • Directorycore/ Construct generici riutilizzati dai construct in app
        • index.ts Punto di ingresso che esporta i construct da app
      • project.json Target di build e configurazione del progetto
    • Directorypackages/common/constructs/src
      • Directoryapp
        • Directorydbs
          • <name>.ts Infrastruttura specifica per il tuo database
      • Directorycore
        • Directoryrdb
          • aurora.ts Costrutto generico del database Aurora

    Il progetto generato utilizza Prisma ORM per definire lo schema del database e generare un client type-safe. Il flusso di lavoro è model-first: aggiungi o aggiorna i file dei modelli Prisma nella directory prisma/models/ del tuo progetto database, quindi genera una migrazione da tali modifiche del modello.

    Esempio di modello User:

    packages/postgres/prisma/models/user.prisma
    model User {
    id Int @id @default(autoincrement())
    firstName String
    lastName String
    }

    Per maggiori dettagli, consulta la guida ufficiale sulla modellazione dei dati con Prisma.

    Il generatore configura automaticamente il target generate per creare un client Prisma TypeScript type-safe ogni volta che costruisci il progetto. Il client viene scritto in generated/prisma (aggiunto a .gitignore).

    Puoi anche generare manualmente il client in qualsiasi momento:

    Terminal window
    pnpm nx generate <your-db-project-name>

    Utilizza il target prisma per eseguire i comandi della CLI di Prisma dalla radice del workspace:

    Terminal window
    pnpm nx run <project>:prisma generate

    Il wrapper runtime in src/prisma.ts esporta:

    • DB_PACKAGE_NAME - la chiave utilizzata sotto il namespace di configurazione runtime database in AWS AppConfig
    • getPrisma() - carica le impostazioni di connessione al database da AWS AppConfig e crea un client Prisma utilizzando l’autenticazione IAM

    Il client automaticamente:

    • Recupera la configurazione del database da AWS AppConfig utilizzando la variabile d’ambiente RUNTIME_CONFIG_APP_ID
    • Genera token di autenticazione temporanei tramite AWS RDS Signer per l’autenticazione IAM
    • Gestisce le connessioni SSL/TLS con validazione dei certificati
    • Gestisce il connection pooling attraverso pool di connessioni database persistenti

    Dopo aver aggiunto o aggiornato i modelli sotto prisma/models/, utilizza migrate dev per generare i file di migrazione e applicarli al tuo database locale contemporaneamente.

    Il target prisma generato avvia automaticamente un database locale tramite Docker prima dell’esecuzione:

    Terminal window
    pnpm nx run <project>:prisma migrate dev

    Se vuoi solo generare i file di migrazione senza applicarli al database locale, aggiungi --create-only:

    Terminal window
    pnpm nx run <project>:prisma migrate dev --create-only

    Questo genera una nuova cartella di migrazione in prisma/migrations ogni volta che il tuo schema cambia:

    • Directoryprisma
      • Directorymigrations
        • Directory20260405013911_initial_migrations
          • migration.sql
        • migration_lock.toml
      • schema.prisma

    Quando deploi lo stack AWS, l’infrastruttura generata applica automaticamente le migrazioni generate al database deployato.

    Quando scarichi i file di migrazione creati da altri sviluppatori, utilizza migrate deploy per applicare quelle migrazioni esistenti al tuo database locale.

    Terminal window
    pnpm nx run <project>:prisma migrate deploy

    In questo flusso di sviluppo locale, migrate deploy applica i file di migrazione al tuo database locale; non deploya il database su AWS.

    Il target prisma generato espone la CLI di Prisma, quindi puoi utilizzarlo per eseguire qualsiasi comando supportato da Prisma contro il database locale. Consulta il riferimento della CLI di Prisma per i comandi disponibili.

    Terminal window
    pnpm nx run <project>:prisma <prisma-command>

    Prisma Studio è un editor visuale per il tuo database locale. Usalo per esplorare tabelle, ispezionare e modificare record, filtrare dati, seguire relazioni ed eseguire SQL raw tramite la console SQL integrata. È utile per verificare le migrazioni e popolare dati di test durante lo sviluppo. Avvialo con:

    Terminal window
    pnpm nx run <project>:prisma studio

    Sebbene questa sezione descriva come connettersi al database da un’API tRPC, serve anche come riferimento per l’uso in qualsiasi altro progetto TypeScript.

    Importa getPrisma dal tuo package database e chiamalo all’interno del tuo gestore per ottenere un client Prisma type-safe:

    packages/api/src/procedures/list-users.ts
    import { getPrisma } from ':my-scope/db';
    import { publicProcedure } from '../init.js';
    import { ListUsersOutputSchema } from '../schema/index.js';
    export const listUsers = publicProcedure
    .output(ListUsersOutputSchema)
    .query(async () => {
    const prisma = await getPrisma();
    return prisma.user.findMany({ orderBy: { id: 'asc' } });
    });

    getPrisma() restituisce un client inizializzato in modo lazy e memorizzato nella cache. Le chiamate successive all’interno dello stesso contesto di esecuzione Lambda riutilizzano il pool di connessioni esistente anziché aprirne uno nuovo.

    Il client Prisma espone modelli completamente tipizzati derivati dal tuo schema prisma/models/, offrendoti type safety end-to-end dal database fino alla risposta della tua API.

    Invece di chiamare getPrisma() in ogni procedura, puoi anche risolverlo una volta in un middleware e allegarlo al contesto tRPC in modo che tutte le procedure downstream possano accedervi direttamente.

    Prima, definisci il plugin in src/middleware/db.ts seguendo lo stesso pattern del middleware generato:

    packages/api/src/middleware/db.ts
    import { getPrisma } from ':my-scope/db';
    import { initTRPC } from '@trpc/server';
    export interface IDbContext {
    db: Awaited<ReturnType<typeof getPrisma>>;
    }
    export const createDbPlugin = () => {
    const t = initTRPC.context<IDbContext>().create();
    return t.procedure.use(async (opts) => {
    const db = await getPrisma();
    return opts.next({
    ctx: {
    ...opts.ctx,
    db,
    },
    });
    });
    };

    Quindi concatenalo su una procedura base nella tua inizializzazione tRPC:

    packages/api/src/init.ts
    import { createDbPlugin } from './middleware/db.js';
    export const dbProcedure = publicProcedure.concat(createDbPlugin());

    Le procedure costruite su dbProcedure ricevono db attraverso il contesto senza dover importare o chiamare getPrisma() direttamente:

    packages/api/src/procedures/list-users.ts
    import { dbProcedure } from '../init.js';
    import { ListUsersOutputSchema } from '../schema/index.js';
    export const listUsers = dbProcedure
    .output(ListUsersOutputSchema)
    .query(async ({ ctx: { db } }) => {
    return db.user.findMany({ orderBy: { id: 'asc' } });
    });

    Il generatore di database relazionale crea infrastruttura CDK o Terraform in base al tuo iacProvider selezionato.

    Il costrutto CDK viene creato in common/constructs. Esempio di utilizzo:

    packages/infra/src/stacks/application-stack.ts
    import { MyDatabase } from ':my-scope/common-constructs';
    export class ApplicationStack extends Stack {
    constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);
    ...
    const db = new MyDatabase(this, 'Db', {
    vpc,
    vpcSubnets: {
    subnetType: SubnetType.PRIVATE_ISOLATED,
    }
    });
    }
    }

    Questo effettua il provisioning di un cluster Aurora con RDS Proxy, credenziali admin, utente database applicativo, registrazione della configurazione runtime e gestore delle migrazioni.

    L’infrastruttura generata crea due utenti database:

    • Utente admin - Creato durante il provisioning del cluster con credenziali memorizzate in AWS Secrets Manager
    • Utente applicativo - Creato tramite una risorsa personalizzata Lambda con autenticazione IAM abilitata e privilegi completi sul database applicativo

    L’utente applicativo viene creato automaticamente con un nome casuale e autenticazione IAM. getPrisma() è già configurato per autenticarsi come questo utente utilizzando token RDS di breve durata, quindi il codice della tua applicazione non gestisce mai le password del database.

    Il tuo VPC dovrebbe includere subnet pubbliche, subnet private con egress e subnet isolate private. Il database può essere eseguito in subnet isolate private, mentre le funzioni Lambda dell’API dovrebbero essere eseguite in subnet private con egress in modo che possano raggiungere i servizi AWS come AppConfig.

    packages/infra/src/stacks/application-stack.ts
    const vpc = new Vpc(this, 'Vpc', {
    subnetConfiguration: [
    {
    name: 'public',
    subnetType: SubnetType.PUBLIC,
    },
    {
    name: 'private_with_egress',
    subnetType: SubnetType.PRIVATE_WITH_EGRESS,
    },
    {
    name: 'private_isolated',
    subnetType: SubnetType.PRIVATE_ISOLATED,
    },
    ],
    });

    Nel tuo stack applicativo, deploya 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 gestore 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 Api(this, 'Api', {
    integrations: Api.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);
    });

    Deploya le funzioni Lambda dell’API in una subnet privata con egress (consigliato) o in una subnet pubblica, non in una subnet isolata privata. A runtime, getPrisma() recupera i dettagli di connessione al database da AWS AppConfig, che è un endpoint di servizio AWS pubblico. Le funzioni Lambda in una subnet isolata privata non hanno accesso internet in uscita e non possono raggiungere AppConfig. Le subnet private con egress instradano il traffico in uscita attraverso un NAT Gateway che si trova in una subnet pubblica.

    L’infrastruttura generata include un RDS Proxy per impostazione predefinita, che si colloca tra la tua applicazione e il cluster Aurora. RDS Proxy fornisce diversi vantaggi:

    • Connection pooling - Mantiene un pool di connessioni al database che possono essere condivise tra le istanze dell’applicazione, riducendo l’overhead di stabilire nuove connessioni
    • Resilienza delle connessioni - Gestisce automaticamente i failover e le riconnessioni durante le sostituzioni delle istanze Aurora o la manutenzione
    • Autenticazione IAM - Supporta l’autenticazione al database basata su IAM, eliminando la necessità di gestire le credenziali del database nel codice dell’applicazione
    • Sicurezza migliorata - Impone la crittografia TLS per tutte le connessioni

    Puoi disabilitare il proxy RDS come segue:

    packages/infra/src/stacks/application-stack.ts
    import { MyDatabase } from ':my-scope/common-constructs';
    const db = new MyDatabase(this, 'Db', {
    ...
    enableRdsProxy: false,
    });

    Quando RDS Proxy è disabilitato, la tua applicazione si connette direttamente all’endpoint del cluster Aurora.

    Per connessioni dirette al cluster Aurora da runtime Lambda Node.js 20 o successivi, configura la funzione Lambda per caricare il bundle CA di Amazon RDS:

    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, consulta i requisiti SSL/TLS di AWS Lambda per le connessioni Amazon RDS e la documentazione TLS di Amazon RDS Proxy. Quando utilizzi RDS Proxy, non è necessario configurare il bundle CA RDS nella tua funzione Lambda.

    L’infrastruttura generata può essere personalizzata per adattarsi ai requisiti del tuo carico di lavoro. I seguenti esempi dimostrano alcune opzioni di personalizzazione comuni disponibili.

    Configura le istanze writer e reader per il tuo cluster Aurora.

    packages/infra/src/stacks/application-stack.ts
    import { MyDatabase } from ':my-scope/common-constructs';
    const db = new MyDatabase(this, 'Db', {
    ...
    writer: ClusterInstance.serverlessV2('writer'),
    readers: [ClusterInstance.serverlessV2('reader')],
    });

    Controlla i limiti di scaling di Aurora Serverless v2 per adattarli al tuo carico di lavoro.

    packages/infra/src/stacks/application-stack.ts
    import { MyDatabase } from ':my-scope/common-constructs';
    const db = new MyDatabase(this, 'Db', {
    ...
    serverlessV2MinCapacity: 0.5,
    serverlessV2MaxCapacity: 8,
    });

    Fissa una versione specifica del motore Aurora.

    Per impostazione predefinita, l’immagine Docker del database locale generata corrisponde alla versione predefinita del motore Aurora. Se modifichi la versione del motore Aurora, si consiglia di utilizzare anche una versione del database Docker locale corrispondente per la massima compatibilità. Consulta le note di rilascio AWS per le versioni di Aurora PostgreSQL e le versioni di Aurora MySQL per identificare la versione del database community corrispondente.

    L’immagine del database locale è configurata nel target serve-local del progetto database generato in project.json. Aggiorna l’argomento dell’immagine passato a scripts/docker-start.ts quando modifichi le versioni del motore.

    engine = PostgreSQL
    packages/infra/src/stacks/application-stack.ts
    import { MyDatabase } from ':my-scope/common-constructs';
    const db = new MyDatabase(this, 'Db', {
    ...
    engineVersion: AuroraPostgresEngineVersion.VER_17_7,
    });
    engine = MySQL
    packages/infra/src/stacks/application-stack.ts
    import { MyDatabase } from ':my-scope/common-constructs';
    const db = new MyDatabase(this, 'Db', {
    ...
    engineVersion: AuroraMysqlEngineVersion.VER_3_12_0,
    });

    La protezione dalla cancellazione è abilitata per impostazione predefinita (deletionProtection: true in CDK, deletion_protection = true in Terraform) per proteggere il cluster Aurora dalla cancellazione accidentale.

    Puoi disabilitare la protezione dalla cancellazione per ambienti in cui è prevista la cancellazione del database, come stack di sviluppo o preview di breve durata.

    packages/infra/src/stacks/application-stack.ts
    import { MyDatabase } from ':my-scope/common-constructs';
    const db = new MyDatabase(this, 'Db', {
    ...
    deletionProtection: false,
    });

    Il costrutto CDK mantiene il cluster Aurora per impostazione predefinita (removalPolicy: RemovalPolicy.RETAIN). Modifica questo comportamento quando vuoi che la cancellazione dello stack CDK crei uno snapshot o distrugga il cluster invece.

    Quando utilizzi RemovalPolicy.DESTROY, la protezione dalla cancellazione deve essere disabilitata prima che il cluster possa essere eliminato.

    packages/infra/src/stacks/application-stack.ts
    import { RemovalPolicy } from 'aws-cdk-lib';
    import { MyDatabase } from ':my-scope/common-constructs';
    const db = new MyDatabase(this, 'Db', {
    ...
    removalPolicy: RemovalPolicy.SNAPSHOT,
    });

    Per un ambiente effimero in cui il database dovrebbe essere eliminato con lo stack:

    packages/infra/src/stacks/application-stack.ts
    import { RemovalPolicy } from 'aws-cdk-lib';
    import { MyDatabase } from ':my-scope/common-constructs';
    const db = new MyDatabase(this, 'Db', {
    ...
    deletionProtection: false,
    removalPolicy: RemovalPolicy.DESTROY,
    });

    La chiave KMS utilizzata per crittografare il cluster Aurora e il suo secret delle credenziali ha la rotazione automatica della chiave abilitata per impostazione predefinita. Disabilitala se la tua policy di sicurezza gestisce la rotazione esternamente.

    packages/infra/src/stacks/application-stack.ts
    import { MyDatabase } from ':my-scope/common-constructs';
    const db = new MyDatabase(this, 'Db', {
    ...
    enableKeyRotation: false,
    });
    engine = MySQL

    Quando si utilizza Aurora MySQL con risposte streaming di API Gateway (ad esempio con httpBatchStreamLink di tRPC), il client MySQL di Prisma mantiene attivo il loop degli eventi di Node.js dopo il completamento di una query, impedendo alla Lambda di svuotare lo stream e terminare la richiesta.

    Per aggirare questo problema, disconnetti esplicitamente il client in un blocco finally dopo ogni query in modo che il loop degli eventi sia libero di uscire e la risposta streaming possa completarsi.

    Opzione 1: per procedura

    export const listExampleTable = publicProcedure
    .output(z.array(ExampleTableSchema))
    .query(async () => {
    const prisma = await getPrisma();
    try {
    return await prisma.exampleTable.findMany();
    } finally {
    await prisma.$disconnect();
    }
    });

    Opzione 2: middleware tRPC

    Se stai utilizzando il pattern middleware, aggiungi la chiamata $disconnect() al middleware in modo che tutte le procedure costruite su di esso siano coperte automaticamente:

    packages/api/src/middleware/db.ts
    import { getPrisma } from ':my-scope/db';
    import { initTRPC } from '@trpc/server';
    export interface IDbContext {
    db: Awaited<ReturnType<typeof getPrisma>>;
    }
    export const createDbPlugin = () => {
    const t = initTRPC.context<IDbContext>().create();
    return t.procedure.use(async (opts) => {
    const db = await getPrisma();
    try {
    return await opts.next({
    ctx: {
    ...opts.ctx,
    db,
    },
    });
    } finally {
    await db.$disconnect();
    }
    });
    };

    I token di autenticazione IAM di RDS scadono dopo 15 minuti. Il client MySQL di Prisma cattura il token IAM come valore statico al momento della chiamata di getPrisma(). Una connessione aperta esistente non è influenzata, ma se è necessario stabilire una nuova connessione dopo la scadenza del token, l’autenticazione fallirà. L’adapter PostgreSQL evita questo problema aggiornando il token dinamicamente ogni volta che il pool apre una nuova connessione, ma l’adapter MySQL non ha un meccanismo equivalente.

    Per attività di lunga durata come job batch o migrazioni di dati, chiama getPrisma() all’inizio di ogni unità di lavoro piuttosto che una volta per l’intera operazione. Poiché getPrisma() crea sempre un client nuovo e recupera un nuovo token IAM per MySQL, questo garantisce che ogni connessione si autentichi con un token valido.