Python DynamoDB
Questo generatore crea un nuovo progetto Python supportato da Amazon DynamoDB, utilizzando PynamoDB per la modellazione delle entità. Genera il codice dell’applicazione e l’infrastruttura necessaria per il provisioning e la gestione di una tabella DynamoDB utilizzando AWS CDK o Terraform, con supporto per il design a tabella singola e sviluppo locale integrato tramite DynamoDB Local.
Utilizzo
Sezione intitolata “Utilizzo”Generare un Progetto DynamoDB
Sezione intitolata “Generare un Progetto DynamoDB”Esegui questo generatore@aws/nx-plugin:py#dynamodb
pnpm nx g @aws/nx-plugin:py#dynamodb yarn nx g @aws/nx-plugin:py#dynamodb npx nx g @aws/nx-plugin:py#dynamodb bunx nx g @aws/nx-plugin:py#dynamodb- 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 - py#dynamodb - Compila i parametri richiesti
- Clicca su
Generate
Componi il tuo comando8
Obbligatorio
Opzioni
Sezione intitolata “Opzioni”nameObbligatoriostringNome del progetto DynamoDB da generare
directorystringPredefinito:packagesLa directory in cui memorizzare il progetto.
frameworkenumPredefinito:pynamodbIl framework da utilizzare per le entità DynamoDB.
pynamodbinfraenumPredefinito:dynamodbInfrastruttura da fornire per la tabella DynamoDB.
dynamodbnoneiacenumPredefinito:inheritIl provider IaC preferito. Per impostazione predefinita viene ereditato dalla selezione iniziale.
inheritcdkterraformsubDirectorystringLa sotto-directory in cui viene posizionato il progetto. Per impostazione predefinita corrisponde al nome del progetto.
tableNamestringIl nome della tabella DynamoDB. Generato automaticamente se non specificato.
preferInstallDependenciesbooleanPredefinito:trueSe 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 crea la seguente struttura di progetto nella directory <directory>/<name>:
Directory<name>
- __init__.py Package exports
- client.py DynamoDB client and table name resolution
Directoryentities
- base.py Base PynamoDB model with GSI declarations
- example.py Example entity definition
- __init__.py Entity exports
- config.json Table configuration including GSI definitions and local development settings
- project.json Project configuration and build targets
Gli script di sviluppo locale sono condivisi tra tutti i progetti DynamoDB (sia TypeScript che Python) e generati una volta in:
Directorypackages/common/scripts/src/dynamodb
- create-local-table.ts Creates the DynamoDB table in the local DynamoDB Local instance
- pull-image.ts Pulls the DynamoDB Local image
- start-container.ts Starts the DynamoDB Local container
Infrastruttura
Sezione intitolata “Infrastruttura”Poiché questo generatore fornisce infrastruttura come codice basata sul tuo iac scelto, creerà un progetto in packages/common che include i costrutti CDK o i moduli Terraform pertinenti.
Il progetto comune di infrastruttura come codice è strutturato come segue:
Directorypackages/common/constructs
Directorysrc
Directoryapp/ Constructs for infrastructure specific to a project/generator
- …
Directorycore/ Generic constructs which are reused by constructs in
app- …
- index.ts Entry point exporting constructs from
app
- project.json Project build targets and configuration
Directorypackages/common/terraform
Directorysrc
Directoryapp/ Terraform modules for infrastructure specific to a project/generator
- …
Directorycore/ Generic modules which are reused by modules in
app- …
- project.json Project build targets and configuration
Directorypackages/common/constructs/src
Directoryapp
Directorydynamodb
- <name>.ts Infrastruttura specifica per la tua tabella
Directorycore
- dynamodb.ts Costrutto generico per tabelle DynamoDB
Directorypackages/common/terraform/src
Directoryapp
Directorydynamodb
Directory<name>
- <name>.tf Modulo specifico per la tua tabella
Directorycore
Directorydynamodb
- dynamodb.tf Modulo generico DynamoDB
Architettura
Sezione intitolata “Architettura”Il progetto distribuito effettua il provisioning della tabella stessa, che qualsiasi progetto a cui è connesso legge e scrive:
Sviluppo Locale
Sezione intitolata “Sviluppo Locale”Avvio di DynamoDB Locale
Sezione intitolata “Avvio di DynamoDB Locale”Il generatore configura un target dev che avvia un’istanza di DynamoDB Local e crea la tabella. Usa il target dev del progetto:
pnpm nx dev <project-name>yarn nx dev <project-name>npx nx dev <project-name>bunx nx dev <project-name>Questo automaticamente:
- Scarica l’immagine DynamoDB Local (target
pull-image) - Avvia un container
- Crea una tabella locale con gli indici definiti in
config.json
Modellazione dei Dati
Sezione intitolata “Modellazione dei Dati”Il progetto generato utilizza PynamoDB per la modellazione delle entità. Tutte le entità devono ereditare dal BaseModel generato — risolve il nome corretto della tabella DynamoDB a runtime, leggendo da AWS AppConfig quando distribuito o da config.json quando eseguito localmente tramite DynamoDB Local. Senza questo, PynamoDB non saprà quale tabella utilizzare. BaseModel utilizza anche il supporto al polimorfismo di PynamoDB per memorizzare più tipi di entità in una singola tabella, seguendo il design a tabella singola di DynamoDB.
Aggiungi o aggiorna i file delle entità in <name>/entities/, utilizzando l’entità di esempio generata come punto di partenza:
from collections.abc import Iteratorfrom datetime import UTC, datetimefrom pynamodb.attributes import UnicodeAttributefrom .base import BaseModel
class ExampleModel(BaseModel, discriminator='ExampleModel'): """ Key design: pk=EXAMPLE#<id>, sk=EXAMPLE#<id> gsi1pk=CATEGORY#<cat>, gsi1sk=EXAMPLE#<id> <- list items by category gsi2pk=EXAMPLE, gsi2sk=<created_at> <- list all items by date """
name = UnicodeAttribute() category = UnicodeAttribute() created_at = UnicodeAttribute() updated_at = UnicodeAttribute()
@classmethod def make_pk(cls, id: str) -> str: return f'EXAMPLE#{id}'
@classmethod def create(cls, id: str, name: str, category: str) -> 'ExampleModel': now = datetime.now(UTC).isoformat() item = cls( pk=cls.make_pk(id), sk=cls.make_pk(id), gsi1pk=f'CATEGORY#{category}', gsi1sk=cls.make_pk(id), gsi2pk='EXAMPLE', gsi2sk=now, name=name, category=category, created_at=now, updated_at=now, ) item.save() return item
# ── Primary index ───────────────────────────────────────────────────────── @classmethod def get_by_id(cls, id: str) -> 'ExampleModel': return cls.get(cls.make_pk(id), cls.make_pk(id))
# ── gsi1_index: partition=category, sort=id ─────────────────────────────── @classmethod def list_by_category(cls, category: str) -> Iterator['ExampleModel']: return cls.gsi1_index.query(f'CATEGORY#{category}')
# ── gsi2_index: partition=type, sort=created_at ─────────────────────────── @classmethod def list_created_between(cls, start: datetime, end: datetime) -> Iterator['ExampleModel']: return cls.gsi2_index.query( 'EXAMPLE', range_key_condition=ExampleModel.gsi2sk.between( start.isoformat(), end.isoformat(), ), scan_index_forward=False, )Per maggiori dettagli, consulta il tutorial di PynamoDB.
Progettazione Basata sui Pattern di Accesso
Sezione intitolata “Progettazione Basata sui Pattern di Accesso”In DynamoDB, la progettazione dello schema inizia con le tue query, non con la forma dei tuoi dati. Prima di scrivere qualsiasi modello, elenca ogni pattern di accesso di cui la tua applicazione ha bisogno, quindi progetta i valori delle chiavi pk, sk e GSI in modo che ogni pattern sia risposto da una singola richiesta alla tabella — nessun JOIN, nessuna lettura sequenziale.
L’ExampleModel generato dimostra questo per tre pattern:
- Get by ID — indice primario,
pk=EXAMPLE#<id>,sk=EXAMPLE#<id> - List by category —
gsi1,pk=CATEGORY#<category> - List by creation date —
gsi2,pk=EXAMPLE, chiave di ordinamento tra timestamp ISO
La convenzione del prefisso di tipo (ad es. EXAMPLE#, CATEGORY#) è deliberata: rende gli elementi auto-descrittivi quando si naviga nella tabella, previene collisioni accidentali di chiavi tra tipi di entità che condividono un indice e consente il filtraggio dei prefissi delle chiavi di ordinamento utilizzando begins_with.
Prima di scrivere una nuova entità, definisci i suoi pattern di chiavi in anticipo in una docstring. L’OrderModel nella sezione successiva segue questa convenzione:
class OrderModel(BaseModel, discriminator='OrderModel'): """ Key design: pk=ORDER#<order_id>, sk=ORDER#<order_id> gsi1pk=USER#<user_id>, gsi1sk=ORDER#<order_id> <- list orders for a user gsi2pk=ORDER, gsi2sk=<created_at> <- list all orders by date """Memorizzazione di Più Tipi di Entità
Sezione intitolata “Memorizzazione di Più Tipi di Entità”Il DiscriminatorAttribute di PynamoDB memorizza un’etichetta di tipo (entity_type) in ogni elemento. Quando si esegue una query tramite BaseModel, questa etichetta viene utilizzata per istanziare automaticamente ogni risultato come la sua sottoclasse corretta — quindi una singola query può restituire un mix di UserModel, OrderModel e qualsiasi altro tipo di entità registrato nella stessa tabella.
Di seguito è riportato un esempio completo con due entità — un UserModel con record OrderModel associati memorizzati nella stessa tabella:
from collections.abc import Iteratorfrom datetime import UTC, datetimefrom pynamodb.attributes import UnicodeAttributefrom .base import BaseModel
class UserModel(BaseModel, discriminator='UserModel'): """ Key design: pk=USER#<user_id>, sk=USER#<user_id> gsi2pk=USER, gsi2sk=<created_at> <- list all users by date """
username = UnicodeAttribute() email = UnicodeAttribute() created_at = UnicodeAttribute()
@classmethod def make_pk(cls, user_id: str) -> str: return f'USER#{user_id}'
@classmethod def create(cls, user_id: str, username: str, email: str) -> 'UserModel': now = datetime.now(UTC).isoformat() item = cls( pk=cls.make_pk(user_id), sk=cls.make_pk(user_id), gsi2pk='USER', gsi2sk=now, username=username, email=email, created_at=now, ) item.save() return item
@classmethod def get_by_id(cls, user_id: str) -> 'UserModel': return cls.get(cls.make_pk(user_id), cls.make_pk(user_id))
@classmethod def list_recent(cls, limit: int | None = None) -> Iterator['UserModel']: return cls.gsi2_index.query('USER', limit=limit, scan_index_forward=False)from collections.abc import Iteratorfrom datetime import UTC, datetimefrom pynamodb.attributes import UnicodeAttributefrom .base import BaseModel
class OrderModel(BaseModel, discriminator='OrderModel'): """ Key design: pk=ORDER#<order_id>, sk=ORDER#<order_id> gsi1pk=USER#<user_id>, gsi1sk=ORDER#<order_id> <- list orders by user gsi2pk=ORDER, gsi2sk=<created_at> <- list all orders by date """
user_id = UnicodeAttribute() total = UnicodeAttribute() created_at = UnicodeAttribute()
@classmethod def make_pk(cls, order_id: str) -> str: return f'ORDER#{order_id}'
@classmethod def create(cls, order_id: str, user_id: str, total: str) -> 'OrderModel': now = datetime.now(UTC).isoformat() item = cls( pk=cls.make_pk(order_id), sk=cls.make_pk(order_id), gsi1pk=f'USER#{user_id}', gsi1sk=cls.make_pk(order_id), gsi2pk='ORDER', gsi2sk=now, user_id=user_id, total=total, created_at=now, ) item.save() return item
@classmethod def get_by_id(cls, order_id: str) -> 'OrderModel': return cls.get(cls.make_pk(order_id), cls.make_pk(order_id))
# ── gsi1_index: partition=user, sort=order_id ──────────────────────────── @classmethod def list_by_user(cls, user_id: str) -> Iterator['OrderModel']: return cls.gsi1_index.query(f'USER#{user_id}')
# ── gsi2_index: partition=type, sort=created_at ─────────────────────────── @classmethod def list_recent(cls, limit: int | None = None) -> Iterator['OrderModel']: return cls.gsi2_index.query('ORDER', limit=limit, scan_index_forward=False)Esporta le nuove entità da __init__.py:
from .user import UserModelfrom .order import OrderModelfrom .example import ExampleModelOverloading dei GSI
Sezione intitolata “Overloading dei GSI”BaseModel fornisce due GSI condivisi (gsi1_index, gsi2_index). Sia UserModel che OrderModel sopra scrivono su gsi2 — ma con valori gsi2pk diversi (USER vs ORDER). Questo è overloading dei GSI: riutilizzare un singolo indice fisico per servire più pattern di accesso indipendenti senza consumare capacità GSI extra.
UserModel—gsi2pk=USER,gsi2sk=<created_at>→ elenca tutti gli utenti per dataOrderModel—gsi2pk=ORDER,gsi2sk=<created_at>→ elenca tutti gli ordini per data
gsi1 può anche essere sovraccaricato quando più tipi di entità condividono lo stesso genitore. Se successivamente aggiungi un ReviewModel che appartiene anche a un utente, puoi assegnargli gsi1pk=USER#<user_id> con una chiave di ordinamento REVIEW#<id> — nessun GSI aggiuntivo necessario. Interrogare gsi1 tramite BaseModel restituisce quindi sia ordini che recensioni per quell’utente in una richiesta, con PynamoDB che istanzia ogni elemento come la sua sottoclasse corretta:
from .base import BaseModelfrom .order import OrderModelfrom .review import ReviewModel
user_id = 'user-123'items = list(BaseModel.gsi1_index.query(f'USER#{user_id}'))
orders = [i for i in items if isinstance(i, OrderModel)]reviews = [i for i in items if isinstance(i, ReviewModel)]Per recuperare solo un tipo di entità da un GSI sovraccaricato, utilizza una condizione di prefisso della chiave di ordinamento:
orders_only = list(BaseModel.gsi1_index.query( f'USER#{user_id}', range_key_condition=BaseModel.gsi1sk.startswith('ORDER#'),))Relazioni Uno-a-Molti
Sezione intitolata “Relazioni Uno-a-Molti”In una relazione uno-a-molti l’entità figlia memorizza un riferimento al suo genitore in una chiave di partizione GSI, rendendo la relazione attraversabile in entrambe le direzioni senza duplicare i dati. L’esempio UserModel / OrderModel sopra è esattamente questo pattern:
- Ottieni un singolo ordine per ID — tabella primaria:
pk=ORDER#<id>,sk=ORDER#<id> - Elenca tutti gli ordini per un utente —
gsi1:pk=USER#<user_id>
Un’alternativa alle ricerche basate su GSI è il pattern item collection: dai agli elementi figli lo stesso pk del loro genitore e usa la chiave di ordinamento per differenziarli. Questo ti consente di recuperare il genitore e tutti i suoi figli in una singola query sulla tabella primaria, senza un GSI:
class OrderModel(BaseModel, discriminator='OrderModel'): """ Key design (item collection): pk=USER#<user_id>, sk=ORDER#<order_id> <- co-located under the parent user """ ...# Retrieve the user and all their orders in one primary-table query# BaseModel dispatches each item to its correct subclass via DiscriminatorAttributeitems = list(BaseModel.query(f'USER#{user_id}'))user = next(i for i in items if isinstance(i, UserModel))orders = [i for i in items if isinstance(i, OrderModel)]Il compromesso: le item collection posizionano tutti i figli sotto una singola chiave di partizione, che è ottimale per la maggior parte dei carichi di lavoro ma può creare una partizione calda con throughput di scrittura estremo. L’approccio GSI (utilizzato negli esempi sopra) mantiene ogni entità nella propria partizione ed è generalmente più sicuro per iniziare.
Relazioni Molti-a-Molti
Sezione intitolata “Relazioni Molti-a-Molti”Le relazioni molti-a-molti richiedono un’entità di giunzione utilizzando il pattern adjacency list: un elemento dedicato che registra ogni collegamento, con la sua chiave GSI che inverte la direzione in modo che la relazione possa essere attraversata in entrambi i modi.
Considera ArticleModel e TagModel, dove un articolo può avere molti tag e un tag può applicarsi a molti articoli:
from collections.abc import Iteratorfrom pynamodb.attributes import UnicodeAttributefrom .base import BaseModel
class ArticleTagModel(BaseModel, discriminator='ArticleTag'): """ Junction entity for the Article ↔ Tag many-to-many relationship.
Key design: pk=ARTICLE#<article_id>, sk=TAG#<tag_name> <- list tags for an article gsi1pk=TAG#<tag_name>, gsi1sk=ARTICLE#<article_id> <- list articles for a tag """
article_id = UnicodeAttribute() tag_name = UnicodeAttribute()
@classmethod def add(cls, article_id: str, tag_name: str) -> 'ArticleTagModel': item = cls( pk=f'ARTICLE#{article_id}', sk=f'TAG#{tag_name}', gsi1pk=f'TAG#{tag_name}', gsi1sk=f'ARTICLE#{article_id}', article_id=article_id, tag_name=tag_name, ) item.save() return item
@classmethod def remove(cls, article_id: str, tag_name: str) -> None: cls.get(f'ARTICLE#{article_id}', f'TAG#{tag_name}').delete()
# ── Primary index: pk=article, sk=tag ───────────────────────────────────── @classmethod def list_tags_for_article(cls, article_id: str) -> Iterator['ArticleTagModel']: return cls.query(f'ARTICLE#{article_id}')
# ── gsi1_index: pk=tag, sk=article ──────────────────────────────────────── @classmethod def list_articles_for_tag(cls, tag_name: str) -> Iterator['ArticleTagModel']: return cls.gsi1_index.query(f'TAG#{tag_name}')Poiché ArticleTagModel utilizza pk=ARTICLE#<article_id> — la stessa partizione dell’articolo stesso — puoi recuperare un articolo e tutti i suoi tag in una singola query sulla tabella primaria:
from .base import BaseModelfrom .article import ArticleModelfrom .article_tag import ArticleTagModel
items = list(BaseModel.query('ARTICLE#article-123'))article = next(i for i in items if isinstance(i, ArticleModel))tags = [i.tag_name for i in items if isinstance(i, ArticleTagModel)]Per ulteriori letture sulla modellazione dei dati DynamoDB, consulta la guida alla modellazione dei dati DynamoDB e Creating a single-table design with Amazon DynamoDB.
Utilizzo del Client DynamoDB
Sezione intitolata “Utilizzo del Client DynamoDB”Il client.py generato esporta due utility chiave:
is_local()— restituisceTruequandoLOCAL_DEV=true, utilizzato per passare tra comportamento locale e AWS.get_table_name()— restituisce il nome della tabella DynamoDB. QuandoLOCAL_DEV=true, legge il nome della tabella dalocalDev.tableNameinconfig.json; altrimenti recupera il nome da AWS AppConfig utilizzando la variabile d’ambienteRUNTIME_CONFIG_APP_IDe lo memorizza nella cache per le chiamate successive.
BaseModel in entities/base.py utilizza entrambi per configurare PynamoDB automaticamente:
- Connessione —
BaseModel.Metaimpostahostdaconfig.jsone codificaregion,aws_access_key_ideaws_secret_access_keyquandois_local()èTrue, puntando PynamoDB all’istanza DynamoDB locale. In AWS, questi vengono lasciati non impostati in modo che PynamoDB utilizzi la catena di credenziali predefinita. - Nome tabella —
BaseModel._get_connection()chiamaget_table_name()prima di ogni operazione, quindi la tabella corretta viene risolta a runtime senza alcuna configurazione manuale.
Arresto di DynamoDB Locale
Sezione intitolata “Arresto di DynamoDB Locale”L’arresto di dev (ad esempio con Ctrl+C) rimuove automaticamente il container DynamoDB Local, ma preserva il volume nominato in modo che i tuoi dati persistano tra i riavvii.
Aggiunta/Rimozione di Global Secondary Index
Sezione intitolata “Aggiunta/Rimozione di Global Secondary Index”I GSI sono definiti in config.json nella radice del progetto sotto la chiave tableConfig.globalSecondaryIndexes. Aggiungi una voce per ogni GSI, quindi rifletti la modifica in BaseModel aggiungendo o rimuovendo la classe GlobalSecondaryIndex corrispondente e gli attributi in <name>/entities/base.py:
{ ... "tableConfig": { "globalSecondaryIndexes": [ { "indexName": "gsi1pk-gsi1sk-index", "partitionKey": "gsi1pk", "sortKey": "gsi1sk" }, { "indexName": "gsi2pk-gsi2sk-index", "partitionKey": "gsi2pk", "sortKey": "gsi2sk" } ] }}Il campo sortKey è facoltativo per i GSI con solo hash-key.
Questo file di configurazione è l’unica fonte di verità letta da tutti i consumatori:
- Sviluppo locale —
devleggeconfig.jsone crea o aggiorna la tabella locale per corrispondere all’elenco dei GSI - CDK — il costrutto legge
config.jsonal momento della sintesi, quindi le modifiche ai GSI si riflettono al prossimocdk deploy - Terraform — il modulo legge
config.jsonal momento del plan/apply
Un GSI per Deployment
Sezione intitolata “Un GSI per Deployment”Connessione alla Tabella
Sezione intitolata “Connessione alla Tabella”In qualsiasi progetto Python, aggiungi il pacchetto DynamoDB come dipendenza del workspace e importa direttamente le classi delle entità:
from my_db_package.entities import ExampleModel
item = ExampleModel.get_by_id('123')Dietro le quinte, ExampleModel chiama get_table_name() per recuperare il nome della tabella da AWS AppConfig a runtime.
Distribuzione della Tabella
Sezione intitolata “Distribuzione della Tabella”Il generatore DynamoDB crea l’infrastruttura CDK o Terraform in base all’iac selezionato.
Il costrutto CDK viene creato in common/constructs. Esempio di utilizzo:
import { MyTable } from '@my-scope/common-constructs';
export class ApplicationStack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props);
const table = new MyTable(this, 'Table'); }}Questo esegue il provisioning di una tabella DynamoDB con:
pk(partition key) esk(sort key), entrambe di tipoString- Global Secondary Indexes come definiti in
config.json - Fatturazione on-demand (
PAY_PER_REQUEST) - Crittografia KMS gestita dal cliente con rotazione automatica delle chiavi
- Point-in-time recovery abilitato
- Protezione dall’eliminazione abilitata
- Nome della tabella registrato in Runtime Config nello spazio dei nomi
dynamodbin AWS AppConfig
Il modulo Terraform viene creato in common/terraform. Esempio di utilizzo:
module "my_table" { source = "../../common/terraform/src/app/dynamodb/my-table"}Questo esegue il provisioning di una tabella DynamoDB con:
pk(partition key) esk(sort key), entrambe di tipoString- Global Secondary Indexes come definiti in
config.json - Fatturazione on-demand (
PAY_PER_REQUEST) - Crittografia KMS gestita dal cliente con rotazione automatica delle chiavi
- Point-in-time recovery abilitato
- Protezione dall’eliminazione abilitata, più una protezione del ciclo di vita
prevent_destroy - Nome della tabella registrato in Runtime Config nello spazio dei nomi
dynamodbin AWS AppConfig
Il modulo core/runtime-config/appconfig espone lo spazio dei nomi dynamodb per impostazione predefinita, quindi il nome della tabella viene distribuito senza ulteriori configurazioni. Se passi namespaces a quel modulo esplicitamente, mantieni dynamodb nell’elenco — altrimenti non viene creato alcun profilo di configurazione per esso e il client della tabella generato non può risolvere il nome della tabella.
Protezione dall’eliminazione
Sezione intitolata “Protezione dall’eliminazione”La tabella è protetta da due protezioni indipendenti, in modo che disattivarne una sola non possa eliminare i tuoi dati:
deletionProtection, applicata da DynamoDB.RemovalPolicy.RETAIN, applicata da CloudFormation, che lascia la tabella al suo posto quando viene rimossa dallo stack.
deletion_protection_enabled, applicata da DynamoDB.lifecycle { prevent_destroy = true }sulla tabella incommon/terraform/src/core/dynamodb/dynamodb.tf, applicata da Terraform, che fa fallire qualsiasi piano che distruggerebbe la tabella.
Eliminazione della tabella
Sezione intitolata “Eliminazione della tabella”Disabilita la protezione per ambienti in cui è prevista l’eliminazione della tabella, come stack di sviluppo o di anteprima di breve durata.
import { RemovalPolicy } from 'aws-cdk-lib';import { MyTable } from '@my-scope/common-constructs';
const table = new MyTable(this, 'Table', { deletionProtection: false, removalPolicy: RemovalPolicy.DESTROY,});module "my_table" { source = "../../common/terraform/src/app/dynamodb/my-table" deletion_protection_enabled = false}prevent_destroy deve essere un letterale — Terraform non consente di fare riferimento a una variabile — quindi non può essere disattivato da main.tf. Rimuovi anche il blocco lifecycle dalla tabella in common/terraform/src/core/dynamodb/dynamodb.tf:
resource "aws_dynamodb_table" "table" { # ...
lifecycle { prevent_destroy = true }}Modalità di fatturazione
Sezione intitolata “Modalità di fatturazione”La tabella utilizza per impostazione predefinita la fatturazione on-demand (PAY_PER_REQUEST). Passa alla capacità con provisioning per carichi di lavoro prevedibili ad alto throughput.
import { BillingMode } from 'aws-cdk-lib/aws-dynamodb';import { MyTable } from '@my-scope/common-constructs';
const table = new MyTable(this, 'Table', { billingMode: BillingMode.PROVISIONED, readCapacity: 5, writeCapacity: 5,});module "my_table" { source = "../../common/terraform/src/app/dynamodb/my-table" billing_mode = "PROVISIONED"}Point-in-time Recovery
Sezione intitolata “Point-in-time Recovery”Il Point-in-time recovery è abilitato per impostazione predefinita, consentendoti di ripristinare la tabella a qualsiasi momento negli ultimi 35 giorni.
Disabilitare il Point-in-time Recovery
Sezione intitolata “Disabilitare il Point-in-time Recovery”import { MyTable } from '@my-scope/common-constructs';
const table = new MyTable(this, 'Table', { pointInTimeRecoverySpecification: { pointInTimeRecoveryEnabled: false },});module "my_table" { source = "../../common/terraform/src/app/dynamodb/my-table" point_in_time_recovery_enabled = false}Crittografia
Sezione intitolata “Crittografia”La tabella è crittografata con una chiave KMS gestita dal cliente per impostazione predefinita, creata automaticamente per te. Passa a una chiave gestita da AWS, alla chiave di proprietà di AWS, o porta la tua chiave KMS, se gestisci la crittografia in modo diverso.
Utilizzare una chiave gestita da AWS
Sezione intitolata “Utilizzare una chiave gestita da AWS”Utilizza la chiave KMS condivisa aws/dynamodb che AWS gestisce per tuo conto. È visibile nella console KMS del tuo account e fatturata per richiesta, ma non c’è alcuna chiave da creare, ruotare o eliminare.
import { TableEncryption } from 'aws-cdk-lib/aws-dynamodb';import { MyTable } from '@my-scope/common-constructs';
const table = new MyTable(this, 'Table', { encryption: TableEncryption.AWS_MANAGED,});module "my_table" { source = "../../common/terraform/src/app/dynamodb/my-table" encryption = "AWS_MANAGED"}Utilizzare la chiave di proprietà di AWS
Sezione intitolata “Utilizzare la chiave di proprietà di AWS”Utilizza una chiave completamente posseduta e gestita da AWS — gratuita, senza alcuna chiave visibile nel tuo account. L’opzione più semplice quando non hai bisogno di una chiave visibile al cliente o all’account per motivi di conformità.
import { TableEncryption } from 'aws-cdk-lib/aws-dynamodb';import { MyTable } from '@my-scope/common-constructs';
const table = new MyTable(this, 'Table', { encryption: TableEncryption.DEFAULT,});module "my_table" { source = "../../common/terraform/src/app/dynamodb/my-table" encryption = "DEFAULT"}Passaggio da CUSTOMER_MANAGED
Sezione intitolata “Passaggio da CUSTOMER_MANAGED”Su una tabella già distribuita, modificare encryption da CUSTOMER_MANAGED (a AWS_MANAGED o DEFAULT) in un singolo terraform apply fallisce: Terraform distrugge la chiave gestita dal cliente prima di aggiornare la tabella, e DynamoDB quindi rifiuta l’aggiornamento perché la chiave è già in attesa di eliminazione.
Aggiralo aggiornando prima la crittografia della tabella direttamente tramite AWS CLI, quindi lasciando che Terraform si allinei e pulisca la chiave orfana:
# For AWS_MANAGED:aws dynamodb update-table --table-name <table-name> \ --sse-specification Enabled=true,SSEType=KMS,KMSMasterKeyId=alias/aws/dynamodb
# For DEFAULT:aws dynamodb update-table --table-name <table-name> --sse-specification Enabled=false
# Then wait for this to report ENABLED (or for SSEDescription to disappear, for DEFAULT):aws dynamodb describe-table --table-name <table-name> --query Table.SSEDescription.StatusQuindi aggiorna encryption nella tua configurazione Terraform ed esegui terraform apply normalmente — Terraform ora deve solo distruggere la chiave già inutilizzata, senza nulla che dipenda da essa.
Utilizzare la propria chiave KMS
Sezione intitolata “Utilizzare la propria chiave KMS”Fornisci una chiave gestita dal cliente esistente invece di farne creare una per te. La chiave deve già concedere al servizio DynamoDB le autorizzazioni necessarie nella propria policy della chiave.
import { Key } from 'aws-cdk-lib/aws-kms';import { MyTable } from '@my-scope/common-constructs';
const key = Key.fromKeyArn(this, 'Key', 'arn:aws:kms:us-east-1:111111111111:key/my-key-id');
const table = new MyTable(this, 'Table', { encryptionKey: key,});module "my_table" { source = "../../common/terraform/src/app/dynamodb/my-table" kms_key_arn = "arn:aws:kms:us-east-1:111111111111:key/my-key-id"}Rotazione della chiave di crittografia
Sezione intitolata “Rotazione della chiave di crittografia”Quando la tabella crea la propria chiave KMS gestita dal cliente (l’impostazione predefinita, e solo quando non hai fornito la tua chiave), quella chiave ha la rotazione automatica delle chiavi abilitata per impostazione predefinita. Disabilitala se la tua policy di sicurezza gestisce la rotazione esternamente.
Disabilitare la rotazione della chiave di crittografia
Sezione intitolata “Disabilitare la rotazione della chiave di crittografia”import { MyTable } from '@my-scope/common-constructs';
const table = new MyTable(this, 'Table', { enableKeyRotation: false,});module "my_table" { source = "../../common/terraform/src/app/dynamodb/my-table" enable_key_rotation = false}Connessioni
Sezione intitolata “Connessioni”Utilizza il generatore connection per integrare questo progetto con altri nel tuo workspace. Le seguenti connessioni coinvolgono questo progetto: