DynamoDBでmap型を更新する(AWS SDK for Go)
はじめに
Users テーブル
Id | Weapons |
---|---|
0 | {"sord": "Normal", "hammer" : "Failure"} |
1 | {"hammer":"Normal"} |
以上のようなテーブルがある時、Id:0のユーザーが新しく武器bowを取得した場合はどのようにクエリを発行すれば良いでしょうか。
問題点
上記のように既にWeaponsのmapが存在している場合は、更新することができます。 しかし、ユーザーはまだ何も武器を持ってない状態(Weaponsはnullの場合)もあり得ます。 この場合はエラーが発生してしまいます。
解決策
DBへ1リクエストでは、この問題点は解決することができないので3リクエストによって解決します。
func main() { ctx := context.Background() config := aws.NewConfig(). WithRegion("ap-northeast-1"). WithEndpoint("http://127.0.0.1:8000"). WithCredentials(credentials.NewStaticCredentials("dummy", "dummy", "dummy")) client := client{ dynamodb.New(session.Must(session.NewSession(config))), } if err := client.update(ctx); err != nil { log.Fatalln(err) } } type client struct { dynamodb *dynamodb.DynamoDB } func (c *client) update(ctx context.Context) error { err := c.updateWithWeapons(ctx) // 1.Weaponsが存在しなければ、ErrCodeConditionalCheckFailedExceptionを返却 if err != nil { if aerr, ok := err.(awserr.Error); ok { if aerr.Code() == dynamodb.ErrCodeConditionalCheckFailedException { err = c.updateNoWeapon(ctx) // 2.Weaponsが存在すれば,ErrCodeConditionalCheckFailedExceptionを返却 if err != nil { if aerr, ok := err.(awserr.Error); ok { if aerr.Code() == dynamodb.ErrCodeConditionalCheckFailedException { // 1と2の間で別プロセスでWeaponsが追加されていた場合 err = c.updateWithWeapons(ctx) // 再びupdateする } } } } } } return err } func (c *client) updateWithWeapons(ctx context.Context) error { updateItemInput := &dynamodb.UpdateItemInput{ TableName: aws.String("Users"), Key: map[string]*dynamodb.AttributeValue{ "Id": {N: aws.String("0")}, }, ExpressionAttributeNames: map[string]*string{ "#WEAPONS": aws.String("Weapons"), "#WEAPON": aws.String("bow"), }, ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{ ":st": {S: aws.String("normal")}, }, UpdateExpression: aws.String("set #WEAPONS.#WEAPON = :st"), ConditionExpression: aws.String("attribute_exists(Id) and attribute_exists(Weapons)"), // WeaponsがなければConditionalCheckFailedExceptionを発生させる } _, err := c.dynamodb.UpdateItemWithContext(ctx, updateItemInput) return err } func (c *client) updateNoWeapon(ctx context.Context) error { updateItemInput := &dynamodb.UpdateItemInput{ TableName: aws.String("Users"), Key: map[string]*dynamodb.AttributeValue{ "Id": {N: aws.String("0")}, }, ExpressionAttributeNames: map[string]*string{ "#WEAPONS": aws.String("Weapons"), }, ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{ ":map": {M: map[string]*dynamodb.AttributeValue{ "bow": {S: aws.String("normal")}, }}, }, UpdateExpression: aws.String("set #WEAPONS = :map"), ConditionExpression: aws.String("attribute_exists(Id) and attribute_not_exists(Weapons)"), // WeaponsがあればConditionalCheckFailedExceptionを発生させる } _, err := c.dynamodb.UpdateItemWithContext(ctx, updateItemInput) return err }
ソースコード: https://github.com/kotaroooo0/for_output/blob/master/dynamodb/main.go