Scrittura condizionale DynamoDB in Node.js (AWS SDK v3)

La parte interessante di una scrittura condizionale in AWS SDK v3 non è la ConditionExpression, che funziona allo stesso modo ovunque ed è trattata in espressioni di condizione DynamoDB. È il percorso di fallimento: v3 ti consegna l'Item che ha vinto sull'errore lanciato, se l'hai chiesto, e non ti dà nulla se non l'hai fatto.

Codice

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

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

// Update the item only if nobody changed it since we read version 7.
const command = new UpdateItemCommand({
  TableName: 'Music',
  Key: {
    Artist: {S: 'Arturo Sandoval'},
    SongTitle: {S: 'Cubano Chant'}
  },
  UpdateExpression: 'SET #upd0 = :updValue0, #version = :newVersion',
  ConditionExpression: 'attribute_exists(#cond0) AND #version = :expectedVersion',
  ExpressionAttributeNames: {
    '#upd0': 'Genre',
    '#version': 'Version',
    '#cond0': 'Artist'
  },
  ExpressionAttributeValues: {
    ':updValue0': {S: 'Latin Jazz'},
    ':expectedVersion': {N: '7'},
    ':newVersion': {N: '8'}
  },
  ReturnValuesOnConditionCheckFailure: 'ALL_OLD'
});

try {
  await client.send(command);
  console.log('Updated to version 8');
} catch (err) {
  if (err.name === 'ConditionalCheckFailedException') {
    // With ReturnValuesOnConditionCheckFailure: 'ALL_OLD', the current item
    // rides back on the exception — no extra read to see what beat you.
    console.log('Lost the race — item is now:', err.Item);
  } else {
    throw err;
  }
}

Spiegazione

  • La verifica fallita è un errore lanciato, non un campo di stato. v3 rifiuta la promise, quindi il percorso della scrittura e quello della corsa persa sono rami diversi. err.name === 'ConditionalCheckFailedException' è il discriminante; tutto il resto va rilanciato, ed è a questo che serve l'else nel blocco. Inghiotti l'intero catch e avrai silenziosamente trasformato un throttling in un no-op.
  • ReturnValuesOnConditionCheckFailure è l'unico modo per vedere chi ti ha battuto. Senza di esso l'errore porta il messaggio e nient'altro, e ti ritrovi con una GetItem di cui non avevi bisogno. Il riferimento dell'API fissa i suoi valori validi a ALL_OLD | NONE e conferma che non consuma capacità di lettura.
  • err.Item è una mappa AttributeValue grezza, della stessa forma della Key che hai inviato, non JavaScript semplice. Passala per unmarshall di @aws-sdk/util-dynamodb prima di confrontare Version con un numero, altrimenti starai confrontando con {N: '9'}.
  • La scrittura fallita viene comunque fatturata. La Developer Guide è esplicita: una condizione che si valuta a false consuma comunque capacità di scrittura, dimensionata sul maggiore tra il vecchio e il nuovo Item. Un loop di retry su una chiave hot è una voce reale in bolletta, quindi metti un tetto ai tentativi.
  • Ogni nome nel blocco ha un alias (#versionVersion, #cond0Artist) perché l'Expression Builder che l'ha generato mette alias incondizionatamente. Qui è più pesante del necessario e non è mai sbagliato: è il compromesso che fa.

Leggere la copia del perdente sull'eccezione

Imposta il Version memorizzato a 9 ed esegui il blocco, che si aspetta 7. DynamoDB Local 3.3.0 lancia, e l'errore intercettato porta con sé:

err.name     ConditionalCheckFailedException
err.message  The conditional request failed
err.$metadata.httpStatusCode  400
err.Item     {
               Artist:     { S: 'Arturo Sandoval' },
               Year:       { N: '1994' },
               Version:    { N: '9' },
               SongTitle:  { S: 'Cubano Chant' },
               AlbumTitle: { S: 'Danzon' }
             }

Quel Version: 9 è tutto il punto. Il retry può tornare dritto dentro l'update con :expectedVersion impostato a 9, senza nessuna lettura in più e senza una finestra in cui un terzo scrittore si infili tra la tua GetItem e il tuo retry.

Elimina ReturnValuesOnConditionCheckFailure dallo stesso comando e riesegui. Stesso name, stesso message, stesso 400, e err.Item è undefined. Nulla ti avverte: il parametro è opzionale, la sua assenza non è un errore, e il codice che legge err.Item comincia semplicemente a loggare undefined in produzione.

Nota anche che un 400 qui non significa richiesta malformata. ValidationException e ConditionalCheckFailedException condividono il codice di stato, e solo uno dei due è un bug: ecco perché il ramo è su err.name e mai sullo stato.

Per vedere una condizione riuscire e fallire sui tuoi dati, con l'espressione scritta per te invece che digitata, 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.