Intermedio5 min de lectura

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.

  • SET escribe o sobrescribe un atributo — escalares, documentos y los idiomas de función if_not_exists y list_append.
  • ADD hace un incremento numérico atómico o una unión de conjuntos, en un viaje de ida y vuelta, sin leer-primero.
  • REMOVE elimina un atributo por completo (o un único elemento de lista por índice).
  • DELETE quita 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áusulaFunciona sobreÚsala para
SETCualquier atributoEscribir/sobrescribir un valor o campo de documento
ADDSolo Number o SetIncrementar atómicamente, o unir a un conjunto
REMOVECualquier atributo o elemento de listaEliminar un atributo; descartar un índice de lista
DELETESolo SetQuitar 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 SET de un contador que lees primero. Usa ADD — leer-modificar-escribir pierde actualizaciones bajo concurrencia. Este es el bug más común de carrito/inventario.
  • list_append sobre una lista ausente. Envuelve el objetivo en if_not_exists o la primera escritura falla.
  • Confundir REMOVE y DELETE. REMOVE descarta el atributo; DELETE resta miembros de un conjunto. Mezclarlos elimina más de lo que pretendías.
  • Olvidar que UpdateItem es un upsert. Si la clave no existe, crea el elemento. Usa una ConditionExpression (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.

Actualizado