概要
GoでFirestoreを扱う際に、構造体を利用したかったのだがその取扱いについて詳細な記載がなかったのでいろいろと試してみました。
ただし今回の記事は、調査するための基盤づくりとしての要素が強いので調査内容自体は簡易的なものです。自身のユースケースに合わせて今後も調査できればと思います。
ちなみに今回は、構造体内でポインタを使った際の動作について検証しました。
(ある程度Firestoreの操作を知っている人向けになってしまうことをお許しください)
始めるにあたって
PlayGroundで動作可能な範疇を超えていたのでGithubでレポジトリを作成しました。簡単な使い方を紹介していたりしていますが、調査が目的なため殴り書きです。悪しからず。
調査内容
概要でも示した通り、Firestoreでドキュメントを作成する際や取得する際にどのような構造体であれば、なんのデータが得られるのかを確認してみました。
具体的には以下のようなケースです。(今回の検証に利用した構造体です)
type City struct {
Name string `firestore:"name"`
Tags []string `firestore:"tags"`
Pop int64 `firestore:"population"`
}
type PC struct {
Name *string `firestore:"name"`
Tags []string `firestore:"tags"`
Pop *int64 `firestore:"population"`
}
PC
はPointerCity
の略称です。
同じフィールド名に対して、ポインタで定義しているかそうでないかが異なっています。
こうした場合、特にポインタで定義した場合どのようになるか以下の観点で気になりました。
今回の検証対象
- 新規書込時に
nil
であれば書き込み対象にならないのか - 読み込み時に存在しないフィールドを取得する際の挙動はどのようになるのか
- ([]stringで構造体として受け取れるのか否か)
検証していないこと
- 更新の際
nil
なフィールドは更新されないのか、はたまた削除されるのか
調査
今回は四つの流れで検証しました。
- Do0
- すべての項目が埋められた
City
構造体でDoc
をCreate
-
City
構造体でDoc
の内容をGet
-
PC
構造体でDoc
の内容をGet
- すべての項目が埋められた
- Do1
- 一部の項目(
Name
のみ)が埋められたCity
構造体でDoc
をCreate
-
City
構造体でDoc
の内容をGet
-
PC
構造体でDoc
の内容をGet
- 一部の項目(
- Do2
- すべての項目が埋められた
PC
構造体でDoc
をCreate
-
City
構造体でDoc
の内容をGet
-
PC
構造体でDoc
の内容をGet
- すべての項目が埋められた
- Do3
- すべての項目が埋められた
PC
構造体でDoc
をCreate
-
City
構造体でDoc
の内容をGet
-
PC
構造体でDoc
の内容をGet
- すべての項目が埋められた
基本的には作成、読み込みの手順を踏んでおり、四つの手法では作成
の部分のみ異なっています。
関連するソースコードは以下のようになります。Githubからご確認ください。
- cmd/structs/main.go
- internal/structs/action.go
- internal/structs/dos.go
- internal/structs/structs.go
結果
それぞれの手順で実行した結果を以下に示します。
一番上にFireStore内での構成。
その下にCity
で取得した際の結果とPC
で取得した際の結果を示しています。
Name: City Full
* users/
* userid0/
* tags: ([]interface {} -> [hello world haha])
* population: (int64 -> 56)
* createdAt: (time.Time -> 2021-07-30 11:57:13.986231 +0000 UTC)
* name: (string -> sasakuna)
* updatedAt: (time.Time -> 2021-07-30 11:57:13.986231 +0000 UTC)
(end)
Name: Data To City
City: &{sasakuna [hello world haha] 56}
Name: Data To PC
City: interface{}(
+ &structs.PC{Name: &"sasakuna", Tags: []string{"hello", "world", "haha"}, Pop: &56},
)
Name: City Partial
* users/
* userid0/
* population: (int64 -> 0)
* tags: (Nil -> <nil>)
* createdAt: (time.Time -> 2021-07-30 11:57:14.002157 +0000 UTC)
* name: (string -> sasakuna)
* updatedAt: (time.Time -> 2021-07-30 11:57:14.002157 +0000 UTC)
(end)
Name: Data To City
City: &{sasakuna [] 0}
Name: Data To PC
City: interface{}(
+ &structs.PC{Name: &"sasakuna", Pop: &0},
)
Name: PC Full
* users/
* userid0/
* name: (string -> sasakuna)
* updatedAt: (time.Time -> 2021-07-30 11:57:14.016314 +0000 UTC)
* tags: ([]interface {} -> [hello world haha])
* population: (int64 -> 56)
* createdAt: (time.Time -> 2021-07-30 11:57:14.016314 +0000 UTC)
(end)
Name: Data To City
City: &{sasakuna [hello world haha] 56}
Name: Data To PC
City: interface{}(
+ &structs.PC{Name: &"sasakuna", Tags: []string{"hello", "world", "haha"}, Pop: &56},
)
Name: PC Partial
* users/
* userid0/
* createdAt: (time.Time -> 2021-07-30 11:57:14.030879 +0000 UTC)
* name: (string -> sasakuna)
* updatedAt: (time.Time -> 2021-07-30 11:57:14.030879 +0000 UTC)
* tags: (Nil -> <nil>)
* population: (Nil -> <nil>)
(end)
Name: Data To City
City: &{sasakuna [] 0}
Name: Data To PC
City: interface{}(
+ &structs.PC{Name: &"sasakuna"},
)
まとめ
今回の結果から以下のようなことがわかりました。
値を割当てなかった際の挙動
タイプ | Create | Get |
---|---|---|
値 | デフォルト値が保管される | デフォルト値が代入される |
ポインタ | Nilとして保管される |
nil のまま |
フィールド値そのものが存在しないようにふるまうわけではなさそう。
感想
よくよく考えたら、ソースコードみれば分かったのでは....?
今後も手っ取り早く動作を確認するのに使わせてもらいます。
というか今回の最大の成果は、Firestore内のデータを可視化したことでは?
あとがき
書いていて、タグ情報ってほかに追加できないのかなって思っていたら、omitempty
という項目がありました。
これを追加すると0値の時にそのフィールドをエンコード対象としないことができるらしく、恐らく多くの人はこちらを使っていることと思います。(これの少し下あたり)
ただ、0を代入することができないという側面もあるので、完全に無駄というわけではなさそう。
ということで、今回の成果は簡単にFirestoreの挙動調査ができるレポジトリをGithubにあげたという点に落ち着きました。