DynamoDB TransactWriteItems in Node.js (AWS SDK v3)

Un TransactWriteItemsCommand riuscito non ti dice quasi nulla: nessun Item, nessun attributo, una risposta vuota. Tutto ciò che ti serve sta sull'eccezione, quindi in SDK v3 il blocco catch qui sotto è la vera superficie dell'API, e vale la pena sapere esattamente cosa ci finisce dentro. (Per capire se una transazione è la chiamata giusta, vedi transazioni DynamoDB.)

Codice

import {DynamoDBClient, TransactWriteItemsCommand} from '@aws-sdk/client-dynamodb';

const client = new DynamoDBClient({region: 'us-east-1'});

// Move one award between two songs — atomically. If the first song has no
// award to give, NEITHER update happens.
const command = new TransactWriteItemsCommand({
  TransactItems: [
    {
      Update: {
        TableName: 'Music',
        Key: {Artist: {S: 'Arturo Sandoval'}, SongTitle: {S: 'Cubano Chant'}},
        UpdateExpression: 'SET #upd0 = #upd0 - :one',
        ConditionExpression: '#upd0 >= :one',
        ExpressionAttributeNames: {'#upd0': 'Awards'},
        ExpressionAttributeValues: {':one': {N: '1'}}
      }
    },
    {
      Update: {
        TableName: 'Music',
        Key: {Artist: {S: 'Arturo Sandoval'}, SongTitle: {S: 'A Mis Abuelos'}},
        UpdateExpression: 'SET #upd0 = if_not_exists(#upd0, :zero) + :one',
        ExpressionAttributeNames: {'#upd0': 'Awards'},
        ExpressionAttributeValues: {':one': {N: '1'}, ':zero': {N: '0'}}
      }
    }
  ]
});

try {
  await client.send(command);
  console.log('Transaction committed');
} catch (err) {
  if (err.name === 'TransactionCanceledException') {
    // One reason per action, in TransactItems order. 'None' means that action
    // was fine — some OTHER action sank the transaction.
    const codes = (err.CancellationReasons ?? []).map((r) => r.Code);
    console.log('Transaction canceled:', codes); // e.g. ['ConditionalCheckFailed', 'None']
  } else {
    throw err;
  }
}

Spiegazione

  • TransactItems — un array ordinato di azioni Put, Update, Delete e ConditionCheck. L'ordine non è l'ordine di esecuzione (la transazione è atomica), ma è l'ordine in cui tornano i motivi di fallimento, che è l'unico motivo per cui importa. I limiti sono trattati più sotto.
  • Cosa lancia davvero v3. Le proprietà proprie dell'oggetto intercettato sono $fault, $retryable, $metadata, name, CancellationReasons, message e __type. Non c'è nessun err.code; err.name è la stringa su cui fare switch, e err.$metadata porta httpStatusCode: 400 più attempts: 1, che è come capisci che l'SDK non ha riprovato la cancellazione di nascosto per te.
  • CancellationReasons è posizionale e sparso. Per la transazione qui sopra arriva come [{"Code":"ConditionalCheckFailed","Message":"The conditional request failed"},{"Code":"None"}]. La voce None non ha affatto una proprietà Message, quindi err.CancellationReasons.map((r) => r.Message.trim()) lancia dentro il tuo gestore di errori proprio sulle azioni riuscite.
  • ReturnValuesOnConditionCheckFailure: 'ALL_OLD' aggiunge un Item al motivo di quell'azione, prima di Code e Message, in DynamoDB JSON grezzo. Gli attributi dell'Item che ha vinto tornano gratis; l'alternativa è una GetItem di follow-up dopo che hai già perso la corsa.
  • Il controllo su err.name ha un buco, e vale la pena sapere quale. Punta due azioni sullo stesso Item e DynamoDB risponde ValidationException con il messaggio Transaction request cannot include multiple operations on one item, e nessun CancellationReasons, perché non è stato tentato nulla. Il ramo else { throw err } qui sopra lo rilancia. È il comportamento corretto, non un bug, ma significa che gli errori strutturali non arrivano mai al tuo logging delle cancellazioni.
  • v3 invia già un ClientRequestToken, anche quando lo ometti. Catturando il body serializzato si vede un UUID nuovo sul filo, e due chiamate send() dello stesso oggetto comando sono partite con due token diversi. Quindi il token protegge una chiamata in volo, non il tuo loop di retry: intercetti, rimandi, e hai un token nuovo e nessuna idempotenza. Forniscine uno tuo se un retry può attraversare il confine di un processo. Riusalo con un parametro cambiato e ottieni IdempotentParameterMismatch invece di una doppia applicazione silenziosa.
  • Solo un altro codice ha bisogno di un percorso dedicato. TransactionConflict significa che una transazione concorrente teneva uno dei tuoi Item, quindi un retry con backoff è la risposta giusta là dove per ConditionalCheckFailed non lo è mai. Il resto è decodificato nella pagina TransactionCanceledException.
  • Costo — ogni Item in una transazione viene scritto due volte sotto il cofano (prepare, poi commit), quindi metti in conto circa 2× la capacità di scrittura di una scrittura semplice. Una scrittura condizionale su un singolo Item ti dà l'atomicità su un Item alla metà di quel costo.

Quale limite colpisci per primo

Il tetto di 100 azioni e quello di 4 MB sono indipendenti, e quello sui byte è quello che sorprende: cento incrementi di contatore non sono nulla, mentre una dozzina di Item grassi possono esaurire l'aggregato da soli. Misura un Item rappresentativo con il calcolatore della dimensione degli Item DynamoDB prima di decidere quante azioni raggruppare. Per leggere gli Item che un'azione toccherà mentre stai ancora scrivendo la condizione, scarica DynoTable.

Esempi correlati

Riferimenti

Ultima verifica 2026-07-28 rispetto alla documentazione ufficiale AWS collegata sopra.

Lavora con DynamoDB senza la Console

Un client desktop veloce per DynamoDB che esegue il vero SQL che DynamoDB non può — JOINs, GROUP BY, aggregazioni — con modifica visuale e un agente AI sulle tue chiavi Bedrock.

Prova gratuita di 30 giorni, senza carta di credito — poi il piano Free senza limiti di tempo.