DynamoDB PutItem in Go (AWS SDK v2)

PutItem scrive un Item intero e sostituisce qualsiasi Item esistente con la stessa chiave primaria (azioni basate su Item spiega in cosa differisce da UpdateItem). Nell'AWS SDK for Go v2 la parte interessante non è la chiamata, è cosa diventano i tuoi valori Go lungo la strada.

Codice

package main

import (
	"context"
	"errors"
	"fmt"
	"log"

	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/service/dynamodb"
	"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
)

func main() {
	ctx := context.TODO()
	cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion("us-east-1"))
	if err != nil {
		log.Fatalf("load config: %v", err)
	}
	client := dynamodb.NewFromConfig(cfg)

	_, err = client.PutItem(ctx, &dynamodb.PutItemInput{
		TableName: aws.String("Music"),
		Item: map[string]types.AttributeValue{
			"Artist":     &types.AttributeValueMemberS{Value: "Arturo Sandoval"},
			"SongTitle":  &types.AttributeValueMemberS{Value: "Cubano Chant"},
			"AlbumTitle": &types.AttributeValueMemberS{Value: "Danzon"},
			"Year":       &types.AttributeValueMemberN{Value: "1994"},
			"Awards":     &types.AttributeValueMemberN{Value: "0"},
		},
		ConditionExpression: aws.String("attribute_not_exists(#cond0) AND attribute_not_exists(#cond1)"),
		ExpressionAttributeNames: map[string]string{
			"#cond0": "Artist",
			"#cond1": "SongTitle",
		},
	})
	if err != nil {
		var ccf *types.ConditionalCheckFailedException
		if errors.As(err, &ccf) {
			fmt.Println("A song with that key already exists — not overwritten")
			return
		}
		log.Fatalf("put item: %v", err)
	}
	fmt.Println("Song written")
}

Spiegazione

attributevalue.MarshalMap è la scorciatoia, e ha delle opinioni. Passare una struct a github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue invece di costruire a mano la map[string]types.AttributeValue qui sopra è la mossa normale. Ecco cosa ha prodotto davvero per una struct con un time.Time, un campo string mai toccato e un *int nil:

Artist     => &types.AttributeValueMemberS{Value:"Arturo Sandoval"}
SongTitle  => &types.AttributeValueMemberS{Value:"Cubano Chant"}
Released   => &types.AttributeValueMemberS{Value:"1994-01-01T00:00:00Z"}
Notes      => &types.AttributeValueMemberS{Value:""}
Rating     => &types.AttributeValueMemberNULL{Value:true}

Tre cose da portarsi via. time.Time diventa una stringa RFC 3339, non un numero Unix, quindi una chiave di ordinamento con timestamp ordina lessicograficamente e si comporterà bene solo se ogni valore è riempito con zeri e nello stesso fuso. Una stringa mai toccata diventa un vero attributo stringa vuota invece di essere omessa. E un puntatore nil diventa NULL, che è un attributo che esiste.

Un attributo NULL sconfigge attribute_not_exists. Questo è quello che ti costa un pomeriggio. Scrivi un Item il cui Rating proveniva da un *int nil, poi proteggi la scrittura successiva con attribute_not_exists(Rating) e fallisce:

ConditionalCheckFailedException: The conditional request failed

DynamoDB ha ragione: l'attributo c'è, e contiene NULL. La correzione è il tag della struct, dynamodbav:"Rating,omitempty", che elimina il campo invece di annullarlo. Lo stesso tag ferma l'attributo stringa vuota, il che conta perché quelli rompono gli indici sparsi e, in un attributo chiave, vengono rifiutati del tutto:

ValidationException: One or more parameter values are not valid. The AttributeValue for a key attribute cannot contain an empty string value. Key: Artist

errors.As, e mai il confronto tra stringhe. L'SDK Go avvolge ogni errore di servizio nell'operation error di smithy, quindi la stringa che ottieni da err.Error() non è il messaggio che ha inviato DynamoDB:

operation error DynamoDB: PutItem, https response error StatusCode: 400, RequestID: 6886fce0-b246-4762-8a8b-0c6dab813040, ConditionalCheckFailedException: The conditional request failed

errors.As(err, &ccf) lo scarta e qui sopra ha restituito true; ccf.ErrorMessage() dà poi il nudo The conditional request failed. Un controllo con strings.Contains sulla stringa esterna funziona oggi e si rompe il giorno in cui l'SDK cambia il formato del suo wrapper.

L'errore tipizzato può portare l'Item che ti ha bloccato. Imposta ReturnValuesOnConditionCheckFailure: types.ReturnValuesOnConditionCheckFailureAllOld sull'input e ccf.Item torna popolato: cinque attributi nell'esecuzione qui sopra, incluso Year come &types.AttributeValueMemberN{Value:"1994"}. Questo ti risparmia il round trip di lettura-dopo-fallimento che la maggior parte dei loop di retry fa a mano. ReturnValues: types.ReturnValueAllOld è l'equivalente sul percorso di successo (ReturnValues).

I numeri sono stringhe, e non è una stranezza di Go. AttributeValueMemberN{Value: "1994"} sembra sbagliato a chiunque venga da un linguaggio tipizzato, ma il tipo N di DynamoDB è un decimale trasportato come testo proprio perché nulla debba passare da un float64. strconv.FormatInt e strconv.FormatFloat sono la conversione; attributevalue la fa per te.

Quello che marshalli è quello che paghi. Un put di un Item da ~15 KB ha riportato "CapacityUnits": 15 con ReturnConsumedCapacity. Le scritture arrotondano per eccesso ogni 1 KB, quindi un campo blob accidentale, o un MarshalMap che ha emesso attributi che volevi scartare, finisce direttamente in bolletta.

Fallo visivamente

Dato che MarshalMap decide cosa finisce davvero nell'Item, vale la pena sapere quanto pesa quell'Item. Il calcolatore della dimensione degli Item DynamoDB gratuito prende il JSON marshalled e restituisce la dimensione in byte e le unità di scrittura a cui arrotonda.

Per scrivere e modificare Item sulle tue tabelle — un form per attributo, selettori di tipo, il risultato ricopiabile come Go — scarica DynoTable.

Esempi correlati

Riferimenti

Riprodotto il 2026-07-28 su go1.26.5 con aws-sdk-go-v2/service/dynamodb v1.62.1 e feature/dynamodb/attributevalue v1.20.55, su DynamoDB Local (amazon/dynamodb-local) sulla porta 9000. I valori marshalled, le stringhe di errore e la lettura di capacità sono output catturato, riportato alla lettera.

Lavora con DynamoDB senza la Console

Un client desktop veloce per DynamoDB che esegue il vero SQL che DynamoDB non può — JOINs, GROUP BY, aggregazioni — con modifica visuale e un agente AI sulle tue chiavi Bedrock.

Prova gratuita di 30 giorni, senza carta di credito — poi il piano Free senza limiti di tempo.