GAE/Go 2nd generationでDatastoreのmutationを使う上でトランザクションに気をつけろ！

僕は普段GAE/Goで開発をしているのですが、自社でdatastoreの機能をまとめたライブラリを作ることになって、[GoDoc](https://pkg.go.dev/cloud.google.com/go/datastore?tab=doc)を読んでいるときに初めて知ったのですが、datastoreにMutationがあるのをしりました！
それでMutationの使い方や使い所の記事を探したのですが、SpannerのMutationの記事と[公式ドキュメント](https://cloud.google.com/datastore/docs/reference/data/rest/v1/projects/commit?hl=ja)しか出て来なかったので、自分で記事を書くことにしました！

# Mutationとは？

自分が探した限り、datastoreのmutationに関する説明は書いていなかったので、何とも言えないんですけど、
Insert, Update, Upsert, Deleteを一括で行うもので、副作用を起こすものですね

で、何がよくなったかというと

- Insert, Update, Upsertが分離された！
- 副作用を起こす処理をまとめられるようになった！

この二点が大きいと感じています！


# どこを気をつけるべきなのか？

まずはどうやって書くことができるのかをみていこうと思います
今までは更新とかは全てPutを使っていて、ある意味Upsertだけだったのですが以下のようにかけるようになりました！

```go

type Book struct {
	ID   int64
	Name string
}

func main() {
	ctx := context.Background()
	book := &Book{
		ID:   1,
		Name: "hoge",
	}
	idKey := datastore.IDKey("Book", 1, nil)
	insert := datastore.NewInsert(idKey, book)
	book.Name = "hoge1"
	update := datastore.NewUpdate(idKey, book)
	book2 := book
	book2.ID = 2
	upsert1 := datastore.NewUpsert(idKey, book2)
	book2.Name = "hoge2"
	upsert2 := datastore.NewUpsert(idKey, book2)
	delete := datastore.NewDelete(book)
	datastoreClient, err := datastore.NewClient(ctx, projectID)
	if err != nil {
		fmt.Errorf("%v", err)
	}

	if err := datastoreClient.Mutate(ctx, insert, update, upsert1, upsert2, delete); err != nil {
		fmt.Errorf("%v", err)
	}	
}
```

go特有の、if err != nil {...}が一度だけMutationを呼ぶことでかなりスッキリ見えますよね。
さらに、ここからが問題で、そもそも、先ほども書きましたが、英語も日本語でも記事もブログも少ないのですが、RESTのドキュメントを覗くとMutationは以下のように書いてあります



The type of commit to perform. Defaults to TRANSACTIONAL.
commitを実行するタイプです。デフォルトではTRANSACTIONALとなってます。

https://cloud.google.com/datastore/docs/reference/data/rest/v1/projects/commit?hl=ja

これを読んで僕は、

　
　
　
「Spanaerとかと同じで、途中でエラーになったらロールバックしてくれるのか！」

　
でも、これ実は**罠**なんですよ！

これを検証して、途中でエラーになるようにしました

```go

type Book struct {
	ID   int64
	Name string
}

func main() {
	ctx := context.Background()
	book := &Book{
		ID:   1,
		Name: "hoge",
	}
	idKey := datastore.IDKey("Book", 1, nil)
	insert1 := datastore.NewInsert(idKey, book)
	book.Name = "hoge1"
	update := datastore.NewUpdate(idKey, book)
	insert2 := datastore.NewInsert(idKey, book) // 二回目にインサートするので、エラーになるはず
	delete := datastore.NewDelete(book)
	datastoreClient, err := datastore.NewClient(ctx, projectID)
	if err != nil {
		fmt.Errorf("%v", err)
	}

	if err := datastoreClient.Mutate(ctx, insert1, update, insert2, delete); err != nil {
		fmt.Errorf("%v", err)
	}
}
```

これを実行すると、途中でエラーになるので、ロールバックされて、Mutationは実行される前の状態になっていて欲しい！
ところが、このままだとそうはいきません、、、、、、
　
　
　

**updateまで実行された状態になってしまいます！！！**

　
　
　
ということで、なんでこうなるかというとMutationの中を見るとわかります！

```go
func (c *Client) Mutate(ctx context.Context, muts ...*Mutation) (ret []*Key, err error) {
	ctx = trace.StartSpan(ctx, "cloud.google.com/go/datastore.Mutate")
	defer func() { trace.EndSpan(ctx, err) }()

	pmuts, err := mutationProtos(muts)
	if err != nil {
		return nil, err
	}
	req := &pb.CommitRequest{
		ProjectId: c.dataset,
		Mutations: pmuts,
		Mode:      pb.CommitRequest_NON_TRANSACTIONAL, // <- ここです！！！！
	}
	resp, err := c.client.Commit(ctx, req)
	if err != nil {
		return nil, err
	}
	// Copy any newly minted keys into the returned keys.
	ret = make([]*Key, len(muts))
	for i, mut := range muts {
		if mut.key.Incomplete() {
			// This key is in the mutation results.
			ret[i], err = protoToKey(resp.MutationResults[i].Key)
			if err != nil {
				return nil, errors.New("datastore: internal error: server returned an invalid key")
			}
		} else {
			ret[i] = mut.key
		}
	}
	return ret, nil
}

// https://github.com/googleapis/google-cloud-go/blob/master/datastore/datastore.go#L651
```

**NON_TRANSACTIONAL**なんですよ！！！
なので、このMutation関数を普通に使ってもロールバックはされません、、、
ではどうしたら良いのかというと、トランザクションをはって使う必要があります。
幸い、TransactionにもMutationがはえているので、それを使うようにするとロールバックされるようになります！

```go

type Book struct {
	ID   int64
	Name string
}

func main() {
	ctx := context.Background()
	book := &Book{
		ID:   1,
		Name: "hoge",
	}
	idKey := datastore.IDKey("Book", 1, nil)
	insert1 := datastore.NewInsert(idKey, book)
	book.Name = "hoge1"
	update := datastore.NewUpdate(idKey, book)
	insert2 := datastore.NewInsert(idKey, book) // 二回目にインサートするので、エラーになるはず
	delete := datastore.NewDelete(book)
	datastoreClient, err := datastore.NewClient(ctx, projectID)
	if err != nil {
		fmt.Errorf("%v", err)
	}

	if _, err := datastoreClient.RunInTransaction(ctx, func(tx *datastore.Transaction) error {
		if _, err := tx.Mutate(insert1, update, insert2, delete); err != nil {
			return multiErrorToSingleError(err)
		}
		return nil
	}); err != nil {
		fmt.Errorf("%v", err)
	}
}
```

こうしておけば、トランザクションがはられて、ロールバックされるので、失敗する前の状態になることができます！！！


# まとめ

- datastoreにもMutationあるよ！
- デフォルトではNON_TRANSACTIONALなので、ロールバックはされないですよ！
- ロールバックして欲しいならば、自分でトランザクションをはりましょうね！

# 参考

- https://pkg.go.dev/cloud.google.com/go/datastore?tab=doc
- https://github.com/googleapis/google-cloud-go/blob/datastore/v1.1.0/datastore/transaction.go
- https://github.com/googleapis/google-cloud-go/blob/master/datastore/datastore.go
- https://cloud.google.com/datastore/docs/reference/data/rest/v1/projects/commit