Firebase Realtime Database を Rest API と Golang でいじってみる

Firebaseの`Realtime Database`は、[iOS/Android SDK](http://qiita.com/tfutada/items/12ec90ef8fa1b9f871ec)の他に、[RestAPI](https://firebase.google.com/docs/database/rest/start
)で直接操作することもできます。`リアルタイム`の恩恵はないですが、アプリ側だけでなく、サーバ側からもDBにデータを仕込む必要があったので調べてました。オフィシャルではないですが[Goのライブラリ](https://github.com/JustinTulloss/firebase)もあったので使ってみました。:grinning:
それから余談ですが、本日11月7日にベルリンにて[Firebaseデブサミ](https://events.withgoogle.com/firebase-dev-summit/)があるようです。Googleには`Developer Advocate`という昔でいうエバンジェリストの職種があり、ドキュメントも分かりやすいですし、YouTubeでチュートリアルビデオがみれたり良いですね。アップルにもあると良いですが。(私は別にGoogleとは何の関係もないし、むしろアップル側ですが。:relaxed:)
## RestAPI

まず、RestAPIの呼び出しに使用するAUTHトークンを払い出します。

Firebaseコンソールの画面左上のギアアイコンから、`Project Settings > SERVICE ACCOUNTS > Database Secrets`を選択します。`SHOW`をクリックするとトークンが表示されますのでコピーしておきます。

![0Screen_Shot_2016-11-06_at_21_49_24.jpg](https://qiita-image-store.s3.amazonaws.com/0/75231/566fd2c8-e29c-fda7-e954-c28ae1306e99.jpeg)


### PUT/PATCH/POST/DELETE

更新系のメソッドです。

RestAPIのエンドポイントはURLの最後に、`.json`をつけます。

`PUT`でJSONノードの書き込み、`PATCH`でノードを部分的に書き込みます。`POST`も書き込みですが、`PUT`との違いはFirebaseが自動的にユニークキーを新規作成して登録します。

```bash:PUT
$ curl -X PUT -d '{                                                            
  "alanisawesome": {
    "name": "Alan Turing",
    "birthday": "June 23, 1912"
  }
}' 'https://fireblog-89e42.firebaseio.com/fireblog/users.json?auth=xoBbyJxuTryxfGBSP0EhQDKdhTLJIuBNgY4vSSs6'
```
:warning:以降、簡単のため`auth`キー省略

コンソールで確認します。
![1Firebase_Console.jpg](https://qiita-image-store.s3.amazonaws.com/0/75231/82b06622-9bc1-ab34-9aeb-737e586879a2.jpeg)



`PATCH`で子ノードを追加します。
:warning:ここで`PUT`を使用すると既存の兄弟ノードが吹っ飛びます。

```bash:PATCH
$ curl -X PATCH -d '{                                                         
  "nickname": "Alan The Machine"
}' 'https://fireblog-89e42.firebaseio.com/fireblog/users/alanisawesome.json'
```
![2Firebase_Console.jpg](https://qiita-image-store.s3.amazonaws.com/0/75231/a2fba024-0252-594e-09ab-1a782f7322a3.jpeg)

パスの指定方法には色々あります。

```bash:キーに、alanisawesome/nickname
curl -X PATCH -d '{                                   
  "alanisawesome/nickname": "Alan is awesome!"
}' 'https://fireblog-89e42.firebaseio.com/fireblog/users.json'
```

```bash:値を、JSON形式
curl -X PATCH -d '{ 
"alanisawesome": {"nickname": "Alan The Machine"}
}' 'https://fireblog-89e42.firebaseio.com/fireblog/users.json'
```

ただ最後の例の場合は、既存の`name`と`birthday`が吹っ飛びます。

最後に`POST`の例です。

```bash:POST
curl -X POST -d '{
  "author": "alanisawesome",
  "title": "The Turing Machine"
}' 'https://fireblog-89e42.firebaseio.com/fireblog/posts.json'
```

![5Firebase_Console.jpg](https://qiita-image-store.s3.amazonaws.com/0/75231/6917b54c-5004-a3db-1a93-d4eef6aef8d8.jpeg)

ブログポスト`posts`のようなトランザクショナルなデータはキーを自動生成させ、ユーザマスタ`users`のようなものはキーを指定して登録する感じでしょうか？(データ構造は[Firebaseのドキュメント](https://firebase.google.com/docs/database/rest/structure-data)に沿ってます。)
### GET

GETでデータを取得します。`print=pretty`をつけるとJSONレスポンスを整形します。

```bash
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](https://dinosaur-facts.firebaseio.com/)を使用します。

身長`height`が4メートル以上の恐竜を検索します。(:warning:JSONレスポンス自体はソートされません。)

```bash
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で始まる恐竜さん。

```bash
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さんのライブラリがシンプルで使いやすいです。

```bash
go get github.com/JustinTulloss/firebase
```

HTTPメソッドはそれぞれ以下のファンクションでラップされてます。

- PUT -> Set()
- PATCH -> Update()
- POST -> Push()
- DELETE -> Remove()
- GET -> Value()

ざっとコードを載せます。

```go:main.go
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"])
	}
}
```

実行します。

```bash
$ 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
```

コンソールで確認します。
![9Firebase_Console.jpg](https://qiita-image-store.s3.amazonaws.com/0/75231/ab5f8a96-6f9c-cff1-ad1a-b81ac49db634.jpeg)

他にもソート機能も実装されてます。Firebaseのレスポンスデータはソートはされていないため、呼び出し側でソートしてあげないといけません。Justinさんのコードはテストケースがしっかり書かれているのでわかりやすいです。
https://github.com/JustinTulloss/firebase/blob/master/firebase_test.go