Update expressions de DynamoDB
Una update expression le dice a UpdateItem cómo mutar un solo elemento: qué atributos
escribir, incrementar, eliminar o fundir en un conjunto. No hay UPDATE … SET … WHERE —
nombras el elemento por su clave y describes el cambio con cuatro palabras clave de
cláusula.
¿Cómo funcionan las update expressions de DynamoDB?
Una update expression de DynamoDB le indica a UpdateItem cómo mutar un elemento usando cuatro cláusulas. SET escribe o sobrescribe un atributo. ADD incrementa un número atómicamente o hace una unión de conjuntos. REMOVE elimina un atributo o un elemento de lista. DELETE quita miembros específicos de un conjunto. Una sola llamada puede llevar las cuatro a la vez.
SETescribe o sobrescribe un atributo — escalares, documentos y los idiomas de funciónif_not_existsylist_append.ADDhace un incremento numérico atómico o una unión de conjuntos, en un viaje de ida y vuelta, sin leer-primero.REMOVEelimina un atributo por completo (o un único elemento de lista por índice).DELETEquita miembros específicos de un conjunto — y solo de un conjunto.
Viniendo de SQL, la trampa es recurrir a SET para todo. ADD y DELETE existen porque
leer-modificar-escribir sobre un contador o un conjunto es una carrera que perderás bajo
concurrencia.
Elige la cláusula según lo que cambias
Una sola llamada UpdateItem puede llevar las cuatro cláusulas a la vez, en el orden
SET … REMOVE … ADD … DELETE. Cada palabra clave aparece como máximo una vez y toma una
lista de acciones separada por comas.
| Cláusula | Funciona sobre | Úsala para |
|---|---|---|
SET | Cualquier atributo | Escribir/sobrescribir un valor o campo de documento |
ADD | Solo Number o Set | Incrementar atómicamente, o unir a un conjunto |
REMOVE | Cualquier atributo o elemento de lista | Eliminar un atributo; descartar un índice de lista |
DELETE | Solo Set | Quitar miembros específicos de un conjunto |
ADD sobre una cadena y DELETE sobre un escalar son errores de validación, no no-ops —
DynamoDB rechaza toda la llamada. Según la
referencia de update-expression de AWS,
ADD está restringido a números y conjuntos, y DELETE a conjuntos.
El ejemplo desarrollado: un carrito de compra
Un elemento por carrito, indexado por CartPK = "CART#c-9f21" y CartSK = "SUMMARY".
Rastrea un OrderTotal acumulado, una lista LineItems, un conjunto de cadenas
PromoCodes y un ItemCount.
SET — escribe los escalares y documentos
SET sobrescribe lo que hubiera ahí. Añade una línea a la lista e incrementa el total en
la misma llamada:
SET OrderTotal = :total,
LineItems = list_append(LineItems, :newItem),
UpdatedAt = :now
list_append(LineItems, :newItem) añade al final; invierte los argumentos —
list_append(:newItem, LineItems) — para anteponer. El orden de los argumentos es el
orden de concatenación, nada más.
Hay un tiro al pie en esa primera llamada: si el carrito es completamente nuevo,
LineItems todavía no existe, y list_append sobre un atributo ausente falla. Protégelo
con if_not_exists:
SET LineItems = list_append(if_not_exists(LineItems, :empty), :newItem)
if_not_exists(LineItems, :empty) devuelve la lista actual si está presente, si no el
respaldo :empty (una lista vacía []). Eso hace que la primera adición y cada adición
posterior usen la misma expresión — una razón real por la que existen estos idiomas.
ADD — incrementa el conteo, atómicamente
Para incrementar ItemCount, no lo leas, sumes uno en tu código y lo vuelvas a hacer
SET. Esa es una carrera de actualización-perdida: dos adiciones concurrentes leen ambas
3, escriben ambas 4, y has perdido una. ADD hace la aritmética del lado del servidor:
ADD ItemCount :one
Con :one = 1, esto es un contador atómico. Las llamadas concurrentes se serializan
sobre el elemento, así que dos adiciones aterrizan como +2. Pasa un número negativo para
decrementar. Si ItemCount está ausente, ADD lo trata primero como 0 — así que nunca
necesitas inicializar el contador.
Puedes construir esta expresión exacta — nombres, valores tipados y la petición
marshalled — en el constructor de expresiones de DynamoDB
sin escapar a mano un solo marcador #name o :value.
REMOVE — descarta un atributo o una línea
REMOVE es como eliminas un atributo por completo (no hay "ponlo a null" — eso solo
escribe un tipo NULL). Limpia un descuento aplicado y descarta la tercera línea en una
llamada:
REMOVE AppliedDiscount, LineItems[2]
LineItems[2] quita el elemento en el índice 2 y desplaza hacia abajo todo lo que viene
después — el índice 3 pasa a ser 2, y así sucesivamente. Si haces REMOVE de dos
índices en una expresión, ambos se evalúan contra la lista original, así que quitar
[2] y [3] juntos descarta el tercer y cuarto elementos como esperarías.
DELETE — quita miembros de un conjunto
PromoCodes es un conjunto de cadenas, así que un cliente que retira un código usa
DELETE, no REMOVE. REMOVE PromoCodes aniquilaría todo el conjunto; DELETE resta los
miembros nombrados:
DELETE PromoCodes :pulled
Con :pulled = el conjunto {"SAVE10"}, solo ese miembro se va. Dos reglas muerden aquí:
un conjunto nunca puede estar vacío, así que eliminar el último miembro quita el atributo
PromoCodes por completo; y el valor debe ser un tipo conjunto que coincida con el atributo
— una cadena pelada es un error de tipo.
Júntalo todo
Una actualización "añade un artículo, aplica una promo, incrementa el conteo" es una sola llamada a través de tres cláusulas:
SET LineItems = list_append(if_not_exists(LineItems, :empty), :newItem),
OrderTotal = OrderTotal + :price
ADD ItemCount :one
DELETE PromoCodes :expiredCode
Fíjate en OrderTotal = OrderTotal + :price — la aritmética dentro de SET opera sobre
el valor existente. No es atómica al modo en que ADD lo es para seguridad-ante-carreras,
pero lee el total actual del lado del servidor en lugar de hacerlo ir y volver a través de
tu código.
Trampas que evitar
- Hacer
SETde un contador que lees primero. UsaADD— leer-modificar-escribir pierde actualizaciones bajo concurrencia. Este es el bug más común de carrito/inventario. list_appendsobre una lista ausente. Envuelve el objetivo enif_not_existso la primera escritura falla.- Confundir
REMOVEyDELETE.REMOVEdescarta el atributo;DELETEresta miembros de un conjunto. Mezclarlos elimina más de lo que pretendías. - Olvidar que
UpdateItemes un upsert. Si la clave no existe, crea el elemento. Usa unaConditionExpression(attribute_exists(CartPK)) cuando quieras decir "solo actualizar".
Para modelar las claves contra las que se ejecutan estas expresiones, consulta diseño de tabla única; para decidir cómo vas a releer el carrito, consulta query vs scan.
Construye y copia cualquiera de estas en el constructor de expresiones, luego prueba DynoTable para ejecutarlas contra tus propias tablas y ver el elemento cambiar en vivo.