Intermediário5 min de leitura

Update Expressions no DynamoDB

Uma update expression diz ao UpdateItem como mutar um único item: quais atributos escrever, incrementar, deletar ou dobrar em um set. Não há UPDATE … SET … WHERE — você nomeia o item pela sua chave e descreve a mudança com quatro palavras-chave de cláusula.

Como funcionam as update expressions do DynamoDB?

Uma update expression do DynamoDB diz ao UpdateItem como mutar um item usando quatro cláusulas. SET escreve ou sobrescreve um atributo. ADD incrementa atomicamente um número ou faz a união em um set. REMOVE deleta um atributo ou um elemento de lista. DELETE remove membros específicos de um set. Uma única chamada pode conter as quatro ao mesmo tempo.

  • SET escreve ou sobrescreve um atributo — escalares, documentos, e os idiomas de função if_not_exists e list_append.
  • ADD faz um incremento numérico atômico ou uma união de set, em uma ida e volta, sem ler primeiro.
  • REMOVE deleta um atributo por completo (ou um único elemento de lista por índice).
  • DELETE remove membros específicos de um set — e só de um set.

Vindo do SQL, a cilada é recorrer a SET para tudo. ADD e DELETE existem porque read-modify-write em um contador ou um set é uma corrida que você vai perder sob concorrência.

Escolha a cláusula pelo que você está mudando

Uma única chamada UpdateItem pode carregar todas as quatro cláusulas de uma vez, na ordem SET … REMOVE … ADD … DELETE. Cada palavra-chave aparece no máximo uma vez e recebe uma lista de ações separada por vírgulas.

CláusulaFunciona emUse para
SETQualquer atributoEscrever/sobrescrever um valor ou campo de documento
ADDSó Number ou SetIncrementar atomicamente, ou unir em um set
REMOVEQualquer atributo ou elemento de listaDeletar um atributo; remover um índice de lista
DELETESó SetRemover membros específicos de um set

ADD em uma string e DELETE em um escalar são erros de validação, não no-ops — o DynamoDB rejeita a chamada inteira. Conforme a referência de update expression da AWS, ADD é restrito a números e sets, e DELETE a sets.

O exemplo prático: um carrinho de compras

Um item por carrinho, chaveado por CartPK = "CART#c-9f21" e CartSK = "SUMMARY". Ele acompanha um OrderTotal corrente, uma lista LineItems, um string set PromoCodes e um ItemCount.

SET — escreva os escalares e documentos

SET sobrescreve o que estava lá. Adicione um item de linha à lista e aumente o total na mesma chamada:

SET OrderTotal = :total,
LineItems = list_append(LineItems, :newItem),
UpdatedAt = :now

list_append(LineItems, :newItem) anexa ao final; inverta os argumentos — list_append(:newItem, LineItems) — para prefixar. A ordem dos argumentos é a ordem da concatenação, nada mais.

Há uma cilada naquela primeira chamada: se o carrinho é novinho, LineItems ainda não existe, e list_append em um atributo ausente falha. Proteja com if_not_exists:

SET LineItems = list_append(if_not_exists(LineItems, :empty), :newItem)

if_not_exists(LineItems, :empty) retorna a lista atual se presente, senão o fallback :empty (uma lista vazia []). Isso faz a primeira adição e toda adição posterior usarem a mesma expressão — uma razão real para esses idiomas existirem.

ADD — incremente a contagem, atomicamente

Para aumentar ItemCount, não leia-o, some um no seu código, e faça SET de volta. Isso é uma corrida de lost-update: duas adições concorrentes ambas leem 3, ambas escrevem 4, e você perdeu uma. ADD faz a aritmética no servidor:

ADD ItemCount :one

Com :one = 1, isso é um contador atômico. Chamadas concorrentes serializam no item, então duas adições caem como +2. Passe um número negativo para decrementar. Se ItemCount estiver ausente, ADD o trata como 0 primeiro — então você nunca precisa semear o contador.

Você pode construir esta expressão exata — nomes, valores tipados, e a requisição marshalled — no DynamoDB expression builder sem escapar à mão um único placeholder #name ou :value.

REMOVE — remova um atributo ou um item de linha

REMOVE é como você deleta um atributo por completo (não há "defina como null" — isso só escreve um tipo NULL). Limpe um desconto aplicado e remova o terceiro item de linha em uma chamada:

REMOVE AppliedDiscount, LineItems[2]

LineItems[2] remove o elemento no índice 2 e desloca tudo depois dele para baixo — o índice 3 vira 2, e assim por diante. Se você fizer REMOVE de dois índices em uma expressão, ambos são avaliados contra a lista original, então remover [2] e [3] juntos remove o terceiro e o quarto elementos como você esperaria.

DELETE — remova membros de set

PromoCodes é um string set, então um cliente puxando um código usa DELETE, não REMOVE. REMOVE PromoCodes arrasaria o set inteiro; DELETE subtrai os membros nomeados:

DELETE PromoCodes :pulled

Com :pulled = o set {"SAVE10"}, só aquele membro vai. Duas regras mordem aqui: um set nunca pode ser vazio, então deletar o último membro remove o atributo PromoCodes por completo; e o valor deve ser de um tipo set que corresponda ao atributo — uma string solta é um erro de tipo.

Junte tudo

Um update "adicione item, aplique um promo, aumente a contagem" é uma chamada por três cláusulas:

SET LineItems = list_append(if_not_exists(LineItems, :empty), :newItem),
OrderTotal = OrderTotal + :price
ADD ItemCount :one
DELETE PromoCodes :expiredCode

Note OrderTotal = OrderTotal + :price — aritmética dentro de SET funciona sobre o valor existente. Não é atômica do jeito que ADD é para segurança contra corridas, mas lê o total atual no servidor em vez de fazer ida e volta dele pelo seu código.

Armadilhas a evitar

  • Fazer SET em um contador que você leu primeiro. Use ADD — read-modify-write perde updates sob concorrência. Esse é o bug de carrinho/estoque mais comum.
  • list_append em uma lista ausente. Envolva o alvo em if_not_exists ou a primeira escrita falha.
  • Confundir REMOVE e DELETE. REMOVE remove o atributo; DELETE subtrai membros de um set. Misturá-los deleta mais do que você pretendia.
  • Esquecer que UpdateItem é um upsert. Se a chave não existe, ele cria o item. Use uma ConditionExpression (attribute_exists(CartPK)) quando você quer dizer "atualize só".

Para modelar as chaves contra as quais essas expressões rodam, veja single-table design; para decidir como você vai ler o carrinho de volta, veja query vs scan.

Construa e copie qualquer uma dessas no expression builder, depois experimente o DynoTable para rodá-las contra suas próprias tabelas e ver o item mudar ao vivo.

Atualizado