DynamoDB PutItem in Go (AWS SDK v2)

PutItem schreibt ein komplettes Item und ersetzt jedes vorhandene Item mit demselben Primary Key (Item-basierte Aktionen behandelt, wie sich das von UpdateItem unterscheidet). Im AWS SDK für Go v2 ist der interessante Teil nicht der Aufruf, sondern das, was aus deinen Go-Werten auf dem Weg nach draußen wird.

Code

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")
}

Erklärung

attributevalue.MarshalMap ist die Abkürzung, und sie hat Meinungen. Ein Struct an github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue zu geben, statt die map[string]types.AttributeValue oben von Hand zu bauen, ist der normale Weg. Das hier hat es tatsächlich für ein Struct mit einer time.Time, einem unberührten string-Feld und einem nil-*int produziert:

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}

Drei Dinge sind daraus mitzunehmen. time.Time wird zu einem RFC-3339-String, nicht zu einer Unix-Zahl — ein Timestamp-Sort-Key sortiert also lexikografisch und verhält sich nur dann korrekt, wenn jeder Wert nullgepolstert und in derselben Zeitzone ist. Ein unberührter String wird zu einem echten Attribut mit leerem String, statt weggelassen zu werden. Und ein nil-Pointer wird zu NULL, also einem Attribut, das existiert.

Ein NULL-Attribut hebelt attribute_not_exists aus. Das ist das, was einen Nachmittag kostet. Schreib ein Item, dessen Rating aus einem nil-*int kam, sichere dann den nächsten Write mit attribute_not_exists(Rating) ab — und er scheitert:

ConditionalCheckFailedException: The conditional request failed

DynamoDB hat recht: Das Attribut ist da und hält NULL. Die Lösung ist das Struct-Tag dynamodbav:"Rating,omitempty", das das Feld weglässt, statt es auf null zu setzen. Dasselbe Tag verhindert das Attribut mit leerem String, was zählt, weil solche Werte Sparse Indexes kaputtmachen und in einem Key-Attribut rundheraus abgelehnt werden:

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, und niemals String-Matching. Das Go-SDK verpackt jeden Service-Fehler in Smithys Operation-Error, der String aus err.Error() ist also nicht die Meldung, die DynamoDB geschickt hat:

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

errors.As(err, &ccf) packt ihn aus und lieferte oben true; ccf.ErrorMessage() gibt dann das nackte The conditional request failed. Eine strings.Contains-Prüfung auf dem äußeren String funktioniert heute und geht an dem Tag kaputt, an dem das SDK sein Wrapper-Format ändert.

Der typisierte Fehler kann das Item mitbringen, das dich blockiert hat. Setze ReturnValuesOnConditionCheckFailure: types.ReturnValuesOnConditionCheckFailureAllOld im Input, und ccf.Item kommt gefüllt zurück: im obigen Lauf fünf Attribute, darunter Year als &types.AttributeValueMemberN{Value:"1994"}. Das spart den Read-nach-Fehlschlag-Roundtrip, den die meisten Retry-Schleifen von Hand machen. ReturnValues: types.ReturnValueAllOld ist das Äquivalent für den Erfolgspfad (ReturnValues).

Zahlen sind Strings, und das ist keine Go-Eigenart. AttributeValueMemberN{Value: "1994"} sieht für jeden falsch aus, der aus einer typisierten Sprache kommt, aber DynamoDBs N-Typ ist eine Dezimalzahl, die als Text transportiert wird — genau damit nichts über ein float64 hin und zurück muss. strconv.FormatInt und strconv.FormatFloat sind die Umwandlung; attributevalue macht es für dich.

Was du marshallst, ist das, wofür du zahlst. Ein Put eines ~15 KB großen Items meldete mit ReturnConsumedCapacity "CapacityUnits": 15. Writes runden pro 1 KB auf — ein versehentliches Blob-Feld oder ein MarshalMap, das Attribute ausgegeben hat, die du weglassen wolltest, taucht also direkt auf der Rechnung auf.

Mach es visuell

Da MarshalMap entscheidet, was tatsächlich im Item landet, lohnt es sich zu wissen, wie schwer dieses Item ist. Der kostenlose DynamoDB-Item-Size-Rechner nimmt das marshallte JSON und liefert die Byte-Größe und die Schreibeinheiten, auf die sie aufrundet.

Um Items gegen deine eigenen Tabellen zu schreiben und zu bearbeiten — ein Formular pro Attribut, Typ-Auswahl, das Ergebnis wieder als Go herauskopieren — lade DynoTable herunter.

Verwandte Beispiele

Referenzen

Am 2026-07-28 auf go1.26.5 mit aws-sdk-go-v2/service/dynamodb v1.62.1 und feature/dynamodb/attributevalue v1.20.55 gegen DynamoDB Local (amazon/dynamodb-local) auf Port 9000 reproduziert. Die marshallten Werte, die Fehler-Strings und der Kapazitätswert sind aufgezeichnete Ausgabe, wortgetreu kopiert.

Mit DynamoDB ohne die Console arbeiten

Ein schneller DynamoDB-Desktop-Client, der das echte SQL ausführt, das DynamoDB nicht kann — JOINs, GROUP BY, Aggregationen — mit visueller Bearbeitung und einem KI-Agenten auf deinen eigenen Bedrock-Schlüsseln.

30 Tage kostenlos testen, keine Kreditkarte — danach der Kostenlos-Tarif ohne Zeitlimit.