Intermédiaire5 min de lecture

Expression attribute names et values dans DynamoDB

Les expressions DynamoDB sont des templates : tu écris des placeholders, puis tu fournis les vrais noms d'attribut et valeurs dans deux maps annexes. #name est un placeholder de nom ; :value est un placeholder de valeur. Confonds les deux et DynamoDB rejette tout l'appel.

Quelle est la différence entre #name et :value dans DynamoDB ?

#name est un placeholder pour un nom d'attribut, fourni via ExpressionAttributeNames ; :value est un placeholder pour une valeur d'attribut, fourni via ExpressionAttributeValues. Utilise #name pour esquiver les mots réservés, les points ou les espaces, et :value pour chaque littéral — DynamoDB n'inline jamais les valeurs. Ils ne sont pas interchangeables ; les intervertir lève une ValidationException.

  • #name substitue un nom d'attribut via ExpressionAttributeNames — utilise-le chaque fois qu'un attribut entre en conflit avec un mot réservé ou contient un point/une espace.
  • :value substitue une valeur via ExpressionAttributeValues — DynamoDB n'inline jamais de littéraux dans le texte d'expression, donc chaque valeur est un placeholder.
  • Ils ne sont pas interchangeables. Un # là où un : appartient est une ValidationException, pas un no-op silencieux.

Venant de SQL, tu inlines les deux — WHERE status = 'published'. DynamoDB n'inline aucun des deux. Cette séparation est ce qui fait trébucher chaque débutant.

Pourquoi les deux maps existent

En SQL la chaîne de requête porte tout : noms de colonnes, littéraux, opérateurs. DynamoDB sépare délibérément la forme de l'expression de ses données.

Les valeurs vont dans leur propre map pour que DynamoDB puisse typer chacune (S, N, BOOL, …) et pour que le parseur n'ait jamais à deviner où finit une chaîne — il n'y a pas de guillemets ni d'échappement à se tromper. Vois les types de données dans DynamoDB pour la liste complète des type tags.

Les noms reçoivent le même traitement pour une raison différente : DynamoDB a une longue liste de mots réservés, et tout attribut correspondant à l'un d'eux ne peut pas apparaître comme nom nu dans une expression. Le placeholder esquive entièrement la réservation.

Le piège des mots réservés

Voici une table d'articles CMS — clé de partition BLOG#<blog>, clé de tri ARTICLE#<slug> — dont les attributs se lisent naturellement mais entrent par hasard en collision avec des mots réservés :

AttributRéservé ?Ce qu'il contient
statusouidraft / published
nameouinom d'affichage de l'auteur
sizeouilongueur en octets rendue
ttlouiexpiration d'archive (epoch)
slugnonslug d'URL

status, name, size et ttl sont tous sur la liste des mots réservés d'AWS, donc ce filtre échoue au premier mot :

FilterExpression  status = :s

DynamoDB renvoie une ValidationException« Attribute name is a reserved keyword; reserved keyword: status ». Le correctif est un placeholder de nom, jamais renommer l'attribut :

FilterExpression           #status = :s
ExpressionAttributeNames   { "#status": "status" }
ExpressionAttributeValues  { ":s": { "S": "published" } }

Le piège : slug n'est pas réservé, donc une requête que tu as testée contre slug marche, et tu supposes que la suivante marchera aussi. Puis status la casse. La liste complète bouge, alors ne la mémorise pas — mets un placeholder sur chaque nom et tu ne te fais jamais mordre.

Mappe chaque valeur, toujours

Les valeurs sont non négociables : il n'y a pas de syntaxe pour un littéral inline. Même un simple nombre reçoit un placeholder. Cette mise à jour marque un article comme publié, estampille sa taille, et pose un TTL d'archive de 30 jours :

UpdateExpression:          SET #status = :s, #size = :sz, #ttl = :exp
ExpressionAttributeNames:  { "#status": "status", "#size": "size", "#ttl": "ttl" }
ExpressionAttributeValues: {
  ":s":   { "S": "published" },
  ":sz":  { "N": "20480" },
  ":exp": { "N": "1719792000" }
}

Note que :sz et :exp sont envoyés comme des chaînes N — le type number de DynamoDB est encodé sur le fil comme une chaîne. La map de valeurs est aussi là où tu réutilises une valeur à travers les clauses : définis :s une fois, référence-le dans une ConditionExpression et une FilterExpression.

Construire ces deux maps à la main est là où se cachent les fautes de frappe. L'Expression Builder génère la chaîne d'expression et les deux maps ensemble, avec les type tags remplis, pour que les placeholders ne puissent pas se désynchroniser.

Les noms pour les chemins imbriqués et délicats

Le placeholder # fait plus qu'esquiver les mots réservés. La syntaxe de chemin de document utilise des points et des crochets, donc un attribut qui contient littéralement un point — disons une clé de métadonnée og.title — est inadressable sans un placeholder :

ProjectionExpression       #og
ExpressionAttributeNames   { "#og": "og.title" }

Sans lui, DynamoDB lit og.title comme « le champ title à l'intérieur de la map og » — une chose entièrement différente. Même histoire pour les noms avec des espaces ou des chiffres en tête. Pour l'imbrication, tu mets un placeholder sur chaque segment : #meta.#author avec #meta et #author tous deux définis.

Noms vs valeurs, côte à côte

#name:value
Substitueun nom d'attributune valeur d'attribut
MapExpressionAttributeNamesExpressionAttributeValues
Préfixe#:
Nécessaire pourmots réservés, points, espacestoujours — pas de littéraux inline
Le mauvais lève une erreurValidationExceptionValidationException

Si une valeur était typée comme un nom, DynamoDB chercherait un attribut appelé published et ta condition ne matcherait jamais comme tu l'entendais — donc l'API échoue bruyamment à la place. Cette rigueur est une fonctionnalité : il n'y a pas de mauvaise réponse silencieuse.

Pièges et étapes suivantes

  • Déclarer un placeholder que tu n'utilises pas — DynamoDB rejette les entrées inutilisées dans l'une ou l'autre map. Construis les maps à partir de l'expression, pas avant elle.
  • Réutiliser :v après avoir édité l'expression — retire une clause et sa valeur peut s'attarder, déclenchant l'erreur d'entrée inutilisée. Le builder les garde au pas.
  • Supposer qu'un nom est sûr parce qu'il a marché une fois — les collisions de mots réservés sont par attribut. Mets des placeholders uniformément et arrête de deviner.

Ces maps apparaissent dans chaque chemin d'écriture, donc elles se marient naturellement avec le single-table design et avec le fait de savoir quand faire un Query vs un Scan avant même d'attacher un filtre.

Génère l'expression plus les deux maps avec l'Expression Builder, puis essaie DynoTable pour les exécuter contre tes propres tables et regarder les placeholders se résoudre.

Mis à jour