FirebaseのRealtime Database
は、iOS/Android SDKの他に、RestAPIで直接操作することもできます。リアルタイム
の恩恵はないですが、アプリ側だけでなく、サーバ側からもDBにデータを仕込む必要があったので調べてました。オフィシャルではないですがGoのライブラリもあったので使ってみました。
それから余談ですが、本日11月7日にベルリンにてFirebaseデブサミがあるようです。GoogleにはDeveloper Advocate
という昔でいうエバンジェリストの職種があり、ドキュメントも分かりやすいですし、YouTubeでチュートリアルビデオがみれたり良いですね。アップルにもあると良いですが。(私は別にGoogleとは何の関係もないし、むしろアップル側ですが。)
RestAPI
まず、RestAPIの呼び出しに使用するAUTHトークンを払い出します。
Firebaseコンソールの画面左上のギアアイコンから、Project Settings > SERVICE ACCOUNTS > Database Secrets
を選択します。SHOW
をクリックするとトークンが表示されますのでコピーしておきます。
PUT/PATCH/POST/DELETE
更新系のメソッドです。
RestAPIのエンドポイントはURLの最後に、.json
をつけます。
PUT
でJSONノードの書き込み、PATCH
でノードを部分的に書き込みます。POST
も書き込みですが、PUT
との違いはFirebaseが自動的にユニークキーを新規作成して登録します。
$ curl -X PUT -d '{
"alanisawesome": {
"name": "Alan Turing",
"birthday": "June 23, 1912"
}
}' 'https://fireblog-89e42.firebaseio.com/fireblog/users.json?auth=xoBbyJxuTryxfGBSP0EhQDKdhTLJIuBNgY4vSSs6'
以降、簡単のためauth
キー省略
PATCH
で子ノードを追加します。
ここでPUT
を使用すると既存の兄弟ノードが吹っ飛びます。
$ curl -X PATCH -d '{
"nickname": "Alan The Machine"
}' 'https://fireblog-89e42.firebaseio.com/fireblog/users/alanisawesome.json'
パスの指定方法には色々あります。
curl -X PATCH -d '{
"alanisawesome/nickname": "Alan is awesome!"
}' 'https://fireblog-89e42.firebaseio.com/fireblog/users.json'
curl -X PATCH -d '{
"alanisawesome": {"nickname": "Alan The Machine"}
}' 'https://fireblog-89e42.firebaseio.com/fireblog/users.json'
ただ最後の例の場合は、既存のname
とbirthday
が吹っ飛びます。
最後にPOST
の例です。
curl -X POST -d '{
"author": "alanisawesome",
"title": "The Turing Machine"
}' 'https://fireblog-89e42.firebaseio.com/fireblog/posts.json'
ブログポストposts
のようなトランザクショナルなデータはキーを自動生成させ、ユーザマスタusers
のようなものはキーを指定して登録する感じでしょうか?(データ構造はFirebaseのドキュメントに沿ってます。)
GET
GETでデータを取得します。print=pretty
をつけるとJSONレスポンスを整形します。
curl 'https://fireblog-89e42.firebaseio.com/fireblog.json?print=pretty'
{
"posts" : {
"-KVtoWZ09s5pQGSwFb9w" : {
"author" : "alanisawesome",
"title" : "The Turing Machine"
}
},
"users" : {
"alanisawesome" : {
"birthday" : "June 23, 1912",
"name" : "Alan Turing",
"nickname" : "Alan The Machine"
}
}
}
簡単なクエリもサポートしてます。Firebaseのサンプルの恐竜DBを使用します。
身長height
が4メートル以上の恐竜を検索します。(JSONレスポンス自体はソートされません。)
curl 'https://dinosaur-facts.firebaseio.com/dinosaurs.json?orderBy="height"&startAt=4&print=pretty'
{
"stegosaurus" : {
"appeared" : -155000000,
"height" : 4,
"length" : 9,
"order" : "ornithischia",
"vanished" : -150000000,
"weight" : 2500
},
"bruhathkayosaurus" : {
"appeared" : -70000000,
"height" : 25,
"length" : 44,
"order" : "saurischia",
"vanished" : -70000000,
"weight" : 135000
}
}
名前がアルファベットのaからmで始まる恐竜さん。
curl 'https://dinosaur-facts.firebaseio.com/dinosaurs.json?orderBy="$key"&startAt="a"&endAt="m"&print=pretty'
{
"bruhathkayosaurus" : {
"appeared" : -70000000,
"height" : 25,
"length" : 44,
"order" : "saurischia",
"vanished" : -70000000,
"weight" : 135000
},
"lambeosaurus" : {
"appeared" : -76000000,
"height" : 2.1,
"length" : 12.5,
"order" : "ornithischia",
"vanished" : -75000000,
"weight" : 5000
},
"linhenykus" : {
"appeared" : -85000000,
"height" : 0.6,
"length" : 1,
"order" : "theropoda",
"vanished" : -75000000,
"weight" : 3
}
}
ただFirebaseのソートは前もってRULE
の設定が必要なので少し複雑です。
Goでやって見る
本題のGoです。Justingさんのライブラリがシンプルで使いやすいです。
go get github.com/JustinTulloss/firebase
HTTPメソッドはそれぞれ以下のファンクションでラップされてます。
- PUT -> Set()
- PATCH -> Update()
- POST -> Push()
- DELETE -> Remove()
- GET -> Value()
ざっとコードを載せます。
package main
import (
"github.com/JustinTulloss/firebase"
"log"
)
type Name struct {
First string `json:",omitempty"`
Last string `json:",omitempty"`
}
func nameAlloc() interface{} {
return &Name{}
}
const (
endpoint = "https://fireblog-89e42.firebaseio.com" // Firebaseエンドポイント
auth = "xoBbyJxuTryxfGBSP0EhQDKdhTLJIuBNgY4vSSs6" // トークン
)
func main() {
c := firebase.NewClient(endpoint + "/foo", auth, nil)
c.Push(&Name{First: "Tim", Last: "Cook"}, nil) // POSTします。
c.Push(&Name{First: "Steve", Last: "Jobs"}, nil) // エラー処理は端折ってます。
// 検索します。
// イテレータで結果セットを受け取ります。内部でgoroutineが呼ばれ、チャネルが返されます。
for n := range c.Iterator(nameAlloc) {
log.Printf("FirstName-LastName: %s - %s", n.Value.(*Name).First, n.Value.(*Name).Last)
}
// 次は、Set() PUTでデータを登録します。構造体でなく、mapでもokay
kids := map[string]map[string]interface{}{
"a": map[string]interface{}{"Name": "Bob", "Age": 14},
"b": map[string]interface{}{"Name": "Alice", "Age": 13},
}
_, err := c.Set("kids", kids, nil) // PUTします。
if err != nil {
log.Fatal(err)
}
// 検索します。
for n := range c.Child("kids").Iterator(nil) {
log.Printf("--- %s", (*n.Value.(*map[string]interface{}))["Name"])
}
}
実行します。
$ go run main.go 22:52:49 ☁ migrate-cloudinary ☂ ⚡
2016/11/06 22:55:46 FirstName-LastName: Tim - Cook
2016/11/06 22:55:46 FirstName-LastName: Steve - Jobs
2016/11/06 22:55:48 --- Bob
2016/11/06 22:55:48 --- Alice
他にもソート機能も実装されてます。Firebaseのレスポンスデータはソートはされていないため、呼び出し側でソートしてあげないといけません。Justinさんのコードはテストケースがしっかり書かれているのでわかりやすいです。
https://github.com/JustinTulloss/firebase/blob/master/firebase_test.go