TransactWriteItems do DynamoDB em Node.js (AWS SDK v3)

Um TransactWriteItemsCommand bem-sucedido não te diz quase nada: sem itens, sem atributos, uma resposta vazia. Tudo o que você precisa está na exceção, então no SDK v3 o bloco catch abaixo é a superfície de API de verdade, e vale saber exatamente o que cai nele. (Para saber quando uma transação é a chamada certa, veja transações no DynamoDB.)

Código

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

Explicação

  • TransactItems — um array ordenado de ações Put, Update, Delete e ConditionCheck. A ordem não é a ordem de execução (a transação é atômica), mas é a ordem em que as razões de falha voltam, que é o único motivo para se importar com ela. Os tetos estão cobertos abaixo.
  • O que a v3 de fato lança. As propriedades próprias do objeto capturado são $fault, $retryable, $metadata, name, CancellationReasons, message e __type. Não existe err.code; err.name é a string na qual fazer o switch, e err.$metadata carrega httpStatusCode: 400 mais attempts: 1, que é como você percebe que o SDK não tentou de novo silenciosamente o cancelamento por você.
  • CancellationReasons é posicional e esparso. Para a transação acima, ele chega como [{"Code":"ConditionalCheckFailed","Message":"The conditional request failed"},{"Code":"None"}]. A entrada None não tem propriedade Message alguma, então err.CancellationReasons.map((r) => r.Message.trim()) lança um erro dentro do seu tratador de erros justamente nas ações que deram certo.
  • ReturnValuesOnConditionCheckFailure: 'ALL_OLD' adiciona um Item à razão daquela ação, antes de Code e Message, em DynamoDB JSON cru. Os atributos do item perdedor voltam de graça; a alternativa é um GetItem de acompanhamento depois de você já ter perdido a corrida.
  • A checagem de err.name tem um buraco, e vale saber qual. Aponte duas ações para o mesmo item e o DynamoDB responde ValidationException com a mensagem Transaction request cannot include multiple operations on one item, e nenhum CancellationReasons, porque nada foi tentado. O ramo else { throw err } acima o relança. Esse é o comportamento correto, não um bug, mas significa que os erros estruturais nunca chegam ao seu log de cancelamentos.
  • A v3 já envia um ClientRequestToken, mesmo quando você o omite. Capturar o corpo serializado mostra um UUID novo na rede, e duas chamadas de send() do mesmo objeto de comando saíram com dois tokens diferentes. Ou seja, o token protege uma chamada em voo, não o seu próprio laço de retry: capture, reenvie, e você tem um token novo e nenhuma idempotência. Forneça o seu próprio se um retry puder cruzar a fronteira de um processo. Reutilize-o com qualquer parâmetro alterado e você recebe IdempotentParameterMismatch em vez de uma dupla aplicação silenciosa.
  • Só um outro código precisa de caminho próprio. TransactionConflict significa que uma transação concorrente segurava um dos seus itens, então um retry com backoff é a resposta certa ali, onde para ConditionalCheckFailed nunca é. O resto está decodificado na página do TransactionCanceledException.
  • Custo — cada item de uma transação é gravado duas vezes por baixo (preparar e depois confirmar), então orce cerca de 2× a capacidade de escrita de uma escrita comum. Uma escrita condicional de item único te dá atomicidade em um item pela metade disso.

Qual limite você atinge primeiro

O teto de 100 ações e o teto de 4 MB são independentes, e o de bytes é o que surpreende as pessoas: cem incrementos de contador não são nada, enquanto uma dúzia de itens gordos consegue esgotar o agregado sozinha. Meça um item representativo com a calculadora de tamanho de item do DynamoDB antes de decidir quantas ações agrupar. Para ler os itens que uma ação vai tocar enquanto você ainda está escrevendo a condição, baixe o DynoTable.

Exemplos relacionados

Referências

Verificado pela última vez em 2026-07-28 contra a documentação oficial da AWS vinculada acima.

Trabalhe com o DynamoDB sem o Console

Um cliente desktop rápido para DynamoDB que roda o SQL de verdade que o DynamoDB não consegue — JOINs, GROUP BY, agregações — com edição visual e um agente de IA com suas próprias chaves do Bedrock.

Teste grátis de 30 dias, sem cartão de crédito — depois o plano Grátis sem limite de tempo.