DynamoDB TransactWriteItems en Node.js (AWS SDK v3)

Un TransactWriteItemsCommand réussi ne te dit presque rien : pas d'éléments, pas d'attributs, une réponse vide. Tout ce dont tu as besoin est sur l'exception, donc en SDK v3 le bloc catch ci-dessous est la vraie surface d'API, et il vaut la peine de savoir exactement ce qui y atterrit. (Pour savoir si une transaction est le bon appel tout court, voir les transactions DynamoDB.)

Code

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;
  }
}

Explication

  • TransactItems — un tableau ordonné d'actions Put, Update, Delete et ConditionCheck. L'ordre n'est pas l'ordre d'exécution (la transaction est atomique), mais c'est l'ordre dans lequel les raisons d'échec reviennent, ce qui est la seule raison de s'en soucier. Les plafonds sont traités plus bas.
  • Ce que v3 lève réellement. Les propriétés propres de l'objet attrapé sont $fault, $retryable, $metadata, name, CancellationReasons, message et __type. Il n'y a pas de err.code ; err.name est la chaîne sur laquelle brancher, et err.$metadata porte httpStatusCode: 400 plus attempts: 1, ce qui te permet de vérifier que le SDK n'a pas discrètement réessayé l'annulation à ta place.
  • CancellationReasons est positionnel et lacunaire. Pour la transaction ci-dessus, il arrive sous la forme [{"Code":"ConditionalCheckFailed","Message":"The conditional request failed"},{"Code":"None"}]. L'entrée None n'a aucune propriété Message, donc err.CancellationReasons.map((r) => r.Message.trim()) lève une exception dans ton gestionnaire d'erreurs, précisément sur les actions qui avaient réussi.
  • ReturnValuesOnConditionCheckFailure: 'ALL_OLD' ajoute un Item à la raison de cette action, devant Code et Message, en JSON DynamoDB brut. Les attributs de l'élément perdant reviennent gratuitement ; l'alternative est un GetItem de suivi alors que tu as déjà perdu la course.
  • Le test sur err.name a un trou, et il vaut la peine de savoir lequel. Vise deux actions sur le même élément et DynamoDB répond ValidationException avec le message Transaction request cannot include multiple operations on one item, et aucun CancellationReasons, parce que rien n'a été tenté. La branche else { throw err } ci-dessus le relance. C'est un comportement correct, pas un bug, mais ça veut dire que les erreurs structurelles n'atteignent jamais ta journalisation des annulations.
  • v3 envoie déjà un ClientRequestToken, même si tu l'omets. Capturer le corps sérialisé montre un UUID neuf sur le fil, et deux appels send() du même objet commande sont partis avec deux jetons différents. Le jeton protège donc un appel en vol, pas ta propre boucle de reprise : attrape, renvoie, et te voilà avec un nouveau jeton et aucune idempotence. Fournis le tien si une reprise peut franchir une frontière de processus. Réutilise-le avec un paramètre modifié et tu obtiens IdempotentParameterMismatch au lieu d'une double application silencieuse.
  • Un seul autre code mérite son chemin de code. TransactionConflict signifie qu'une transaction concurrente détenait l'un de tes éléments : une reprise avec backoff est la bonne réponse, là où elle ne l'est jamais pour ConditionalCheckFailed. Les autres sont décodés sur la page TransactionCanceledException.
  • Coût — chaque élément d'une transaction est écrit deux fois en dessous (préparation, puis validation), donc prévois environ 2× la capacité d'écriture d'une écriture simple. Une écriture conditionnelle sur un seul élément te donne l'atomicité sur cet élément pour la moitié.

Quelle limite tu atteins en premier

Le plafond de 100 actions et celui de 4 Mo sont indépendants, et c'est celui des octets qui surprend : cent incréments de compteur, ce n'est rien, alors qu'une douzaine d'éléments volumineux peuvent épuiser l'agrégat à eux seuls. Mesure un élément représentatif avec le calculateur de taille d'élément DynamoDB avant de décider combien d'actions regrouper. Pour lire les éléments qu'une action va toucher pendant que tu écris encore la condition, télécharge DynoTable.

Exemples liés

Références

Dernière vérification le 2026-07-28 par rapport à la documentation officielle AWS liée ci-dessus.

Travaille avec DynamoDB sans la Console

Un client de bureau rapide pour DynamoDB qui exécute le vrai SQL que DynamoDB ne peut pas — JOINs, GROUP BY, agrégations — avec édition visuelle et un agent IA sur tes propres clés Bedrock.

Essai gratuit de 30 jours, sans carte bancaire — ensuite la formule Gratuit, sans limite de durée.