Firebaseのgo実装を試す

こんにちは!eurekaのAPIチームでエンジニアをやっている@takochuu です。

Firebaseはクライアントアプリからサーバーレスで機能を実現する時に使うもの、というイメージがあったのですが、先日リポジトリを漁っていたら Firebase SDKのgo実装が公開されていることに気づいたので早速試してみます。

※決してモテることを期待しているわけではありません

FirebaseのGoDocを参照すると、SDKを通してアクセスできるサービスは複数あることがわかります。今回は個人でも試しやすいFirestoreを試すことにします。

※まだベータ実装なので様々な制限があるため、Production環境で利用する際は気をつけてください

そもそもFirestoreとは、それまで提供されていたRealtime Databaseの後継に当たるクラウドホストのNoSQLデータベースです。RDBMSと違い、Tableとrowsでデータを表現するのではなく、collection / document / data という3つの概念でデータを表現します。

階層型のデータ構造が扱えるのもRDBMSとは大きな違いです(BigqueryとかDynamoDBも扱えますよね)

では、さっそく実装に入ります。go getコマンドでパッケージを取得しておいてください。

package main

import (
"context"

"cloud.google.com/go/firestore"
"github.com/golang/glog"
)

func main() {
ctx := context.Background()
app := initFirebase()
client, err := app.Firestore(ctx)
if err != nil {
glog.Errorln(err)
}
}
func initFirebase() *firebase.App {
opt := option.WithCredentialsFile("/path/to/json.json")
app, err := firebase.NewApp(context.Background(), nil, opt)
if err != nil {
glog.Errorln("Error")
}
return app
}

mainパッケージを用意して、Clientを作成します。今回はCredentialファイルを配置してAppをinitする方法で実装を行います。

これで初期化は完了したので、次はusers-devというcollection内のtestというdocumentにネストしたデータを書き込みます。

   type state struct {
Height int `firestore:"height"`
Sex string `firestore:"sex"`
Age int `firestore:"age"`
}

type writeData struct {
UserID int64 `firestore:"user_id"`
Email string `firestore:"email"`
State state `firestore:"state"`
}
   // users-devというCollection内の
collection := client.Collection("users-dev")
doc := collection.Doc("test")
result, err := doc.Set(ctx, writeData{
UserID: 40000,
Email: "takochuu@hogehoge.jp",
State: state{
Sex: "male",
Height: 160,
Age: 25,
},
})
if err != nil {
glog.Errorln(err)
}
}

上記のコードを実行すると、管理画面上で下記のようにデータが挿入されているのがわかります。

今度は、書き込んだデータを取得してみます。

SDKでは複数種類データ取得の方法が実装されているのでそちらを紹介します。

collection := client.Collection("users-dev")

// documentを指定して取得
doc := collection.Doc("test")
snapshot, err := doc.Get(ctx)
if err != nil {
glog.Errorln(err)
}
dump.Dump("===========================")
data := snapshot.Data()
if err != nil {
glog.Errorln(err)
}
dump.Dump(data["user_id"])

// 構造体にマッピング
ent := writeData{}
if err = snapshot.DataTo(&ent); err != nil {
glog.Errorln(err)
}
dump.Dump("===========================")
dump.Dump(ent)

// Queryで取得
q := client.Collection("users-dev").Where("user_id", "=", 40000)
dump.Dump("===========================")
dump.Dump(q.Documents(ctx))
dump.Dump("===========================")

結果はこのようになります。

"===========================”
40000 (int64)
“===========================”
writeData {
UserID: 40000 (int64),
Email: “takochuu@hogehoge.jp”,
State: state {
Height: 160 (int),
Sex: “male”,
Age: 25 (int)
}
}
“===========================”
(0xc420302140) &DocumentIterator {
iter: interface(docIterator) ,
err: interface(error)
}
“===========================”

ここまででわかるように、かなり柔軟にデータを取得できます。

サービスで適用する際は、構造体にマッピングする方法以外は型が明確ではないので、パフォーマンスが気になるのでベンチマークを取って比較してみることにします。

※Queryは複数件なので除外します

package main

import (
"context"
"testing"

"github.com/golang/glog"
)

func BenchmarkSnapshot(t *testing.B) {
ctx := context.Background()
app := initFirebase()
client, err := app.Firestore(ctx)
if err != nil {
glog.Errorln(err)
}

collection := client.Collection("users-dev")

// documentを指定して取得
doc := collection.Doc("test")
snapshot, err := doc.Get(ctx)
if err != nil {
glog.Errorln(err)
}

t.ResetTimer()
for i := 0; i < t.N; i++ {
_ = snapshot.Data()
}
t.StopTimer()
}

func BenchmarkMapping(t *testing.B) {
ctx := context.Background()
app := initFirebase()
client, err := app.Firestore(ctx)
if err != nil {
glog.Errorln(err)
}

type state struct {
Height int `firestore:"height"`
Sex string `firestore:"sex"`
Age int `firestore:"age"`
}

type writeData struct {
UserID int64 `firestore:"user_id"`
Email string `firestore:"email"`
State state `firestore:"state"`
}

collection := client.Collection("users-dev")

// documentを指定して取得
doc := collection.Doc("test")
snapshot, err := doc.Get(ctx)
ent := writeData{}
t.ResetTimer()
for i := 0; i < t.N; i++ {
_ = snapshot.DataTo(&ent)
}
t.StopTimer()
}

意外にも、型が明確なBenchmarkMappingの方が実行速度は遅いという結果になりました。allocateの数も少ないですね。

> go test -v -bench . -benchmem
goos: darwin
goarch: amd64
pkg: github.com/takochuu/firestore-sample
BenchmarkSnapshot-4 1000000 1206 ns/op 728 B/op 9 allocs/op
BenchmarkMapping-4 1000000 1860 ns/op 488 B/op 16 allocs/op
PASS
ok github.com/takochuu/firestore-sample 12.098s

今回はこのあたりで、次回はGoのBenchについてでも書いてみたいと思います。

エウレカのAPIチームでは、Goで実装したくてたまらねぇぜ!って方やビジネスを成功させたいぜ!と思ってるエンジニアと一緒に働きたいと思っています。メンバーを常に募集していますので、少しでも興味を持っていただけましたら、下記Wantedlyからご応募をよろしくおねがいします。