emahiro/b.log

日々の勉強の記録とか育児の記録とか。

Firestore の Go SDK でドキュメントを操作する

Overview

firestore でドキュメントを操作するベースとなる方法を記載します。 ※ Go の実装で書いてます。

ref: https://godoc.org/cloud.google.com/go/firestore

Firestore の基本的な操作

取得

Get

ctx := context.Background()
client, err := firestore.NewClient(ctx, "$projectID")
if err != nil {
    // TODO: Handle error.
}
ref, err := client.Collection("$CollectionName").Doc("$DocumentID").Get(ctx)
if err != nil {
    // TODO: Handle error
}

ref: https://godoc.org/cloud.google.com/go/firestore#DocumentRef.Get

GetAll

ctx := context.Background()
client, err := firestore.NewClient(ctx, "$projectID")
if err != nil {
    // TODO: Handle error.
}
dss, err := client.Collectipn("$CollectionName").Documents(ctx).GetAll()
if err != nil {
    // TODO: Handle error
}

dsts := make([]*DistStruct, len(refs))
for i, ss := range dss {
    var dst = DistSturct{}
    if err := ss.DataTo(&dst); err != nil {
        // Handle Error
    }
    dsts[i] = &dst
}

ref:

追記

公式のドキュメントに記載されている実装方法は https://godoc.org/cloud.google.com/go/firestore#DocumentIterator を取得してからループで一つ一つマッピングしていく実装方針が記載されていますが、https://godoc.org/cloud.google.com/go/firestore#DocumentSnapshot を先に取り出したほうがその後 Slice にマッピングする時に Slice を length 指定でメモリ効率化できるので実装方針としてはそちらを採用する方がいいのでは?と思いました。

Save

ctx := context.Background()
client, err := firestore.NewClient(ctx, "$projectID")
if err != nil {
    // TODO: Handle error.
}
src := map[string]interface{}{}
ref, result, err := client.Collection("$CollectionName").Add(ctx)

ref: https://godoc.org/cloud.google.com/go/firestore#CollectionRef.Add

更新

ctx := context.Background()
client, err := firestore.NewClient(ctx, "$projectID")
if err != nil {
    // TODO: Handle error.
}
updates := []firestore.Update {
    {Path: "$updateTargetField", Value: interface{}{} }
}
if err := client.Collection("$DocumentName").Update(ctx, updates); err != nil {
    // TODO: Handle error.
}

ref: https://godoc.org/cloud.google.com/go/firestore#DocumentRef.Update

firestore.FieldPath

Update 構造体の中に FieldPath がありますが、ある Document 内部の Filed が入れ子の場合に特定の field を更新したい場合に使います。

ref: https://firebase.google.com/docs/reference/node/firebase.firestore.FieldPath

 // A FieldPath is a non-empty sequence of non-empty fields that reference a value.
 //
 // A FieldPath value should only be necessary if one of the field names contains
 // one of the runes ".˜*/[]". Most methods accept a simpler form of field path
 // as a string in which the individual fields are separated by dots.
 // For example,
 //   []string{"a", "b"}
 // is equivalent to the string form
 //   "a.b"
 // but
 //   []string{"*"}
 // has no equivalent string form.
 type FieldPath []string

ref: https://github.com/googleapis/google-cloud-go/blob/master/firestore/fieldpath.go#L31-L43

そのため以下のような構造のドキュメントを考えた時に

type Article struct {
    User User `json:"user" firestore:"user"`
}

type User struct {
    Name string `json:"name" firestore:"name"`
    Age    int64  `json:"age" firestore:"age"`
}

Article 内部のユーザーの名前を変更したい時に以下のような FieldPath を組み立てることになります。

fp := []string{"user", "name"}
update := []firestore.Update {FieldPath: fp, Value: "Taro"}
if err := client.Collection("Article").Update(ctx, update); err != nil {
    // TODO: Handle error.
}

Transaction

if err := client.RunInTransaction(ctx, func(ctx context.Context, tx *firestore.Transaction){
    // Transaction 
}); err != nil {
    // TODO: Handle error.
}

ref: https://godoc.org/cloud.google.com/go/firestore#Transaction