twitchtv/twirp を試した
gRPCのようなフレームワークで、違いはHTTP 1.1で動くこととJSONをサポートしてること。
インストール
protoc-gen-twirpの他にprotocとprotoc-gen-goも必要。
$ go get github.com/twitchtv/twirp/protoc-gen-twirp $ brew install protobuf $ go get github.com/golang/protobuf/protoc-gen-go
protoファイル
まずprotoファイルを書く。
$ mkdir proto
$ vi proto/hello.proto
syntax = "proto3"; package utahta.twirp.example.helloworld; option go_package = "helloworld"; service HellowWorld { rpc Hello(HelloReq) returns (HelloResp); } message HelloReq { string subject = 1; } message HelloResp { string test = 1; }
protocする
protoファイルからgoファイルをつくる。
$ mkdir helloworld $ protoc --proto_path=./proto --twirp_out=./helloworld --go_out=./helloworld ./proto/hello.proto $ ls helloworld hello.pb.go hello.twirp.go
サーバを書く
$ mkdir server
$ vi server/main.go
package main import ( "context" "fmt" "net/http" "github.com/utahta/twirp-example/helloworld" ) type Server struct{} func (s *Server) Hello(ctx context.Context, req *helloworld.HelloReq) (*helloworld.HelloResp, error) { return &helloworld.HelloResp{ Test: fmt.Sprintf("Subject: %s", req.Subject), }, nil } func main() { s := &Server{} handler := helloworld.NewHellowWorldServer(s, nil) http.ListenAndServe(":8881", handler) }
クライアントを書く
$ mkdir client
$ vi client/main.go
package main import ( "context" "fmt" "net/http" "github.com/utahta/twirp-example/helloworld" ) func main() { c := helloworld.NewHellowWorldProtobufClient("http://localhost:8881", http.DefaultClient) resp, err := c.Hello(context.Background(), &helloworld.HelloReq{Subject: "hello twirp"}) if err != nil { panic(err) } fmt.Printf("%#v\n", resp) }
最終的に次のようなディレクトリ構成になった。
. ├── client │ └── main.go ├── helloworld │ ├── hello.pb.go │ └── hello.twirp.go ├── proto │ └── hello.proto └── server └── main.go
実行する
まずサーバを実行。
$ go run server/main.go
次にクライアントを実行する。 すると結果が返ってくる。
$ go run client/main.go &helloworld.HelloResp{Test:"Subject: hello twirp"}
curlで実行する
JSONに対応しているのでcurlを使ってさくっとリクエストできる。
$ curl -H 'Content-Type:application/json' -X POST -d '{"subject":"hello curl"}' "http://127.0.0.1:8881/twirp/utahta.twirp.example.helloworld.HellowWorld/Hello"
雑感
シュッと書いたらProtocolBuffersとJSONで会話できるようになってすごい。便利。
学習コストの低さがなによりいい。
2018年にもなって xargs でハマった
BSD と GNU 版の挙動の違いにハマった。
以下、ミニマムに試した結果。
環境
BSD xargs: macOS 10.12.6
GNU xargs: ubuntu 18.04 xargs (GNU findutils) 4.7.0-git
同じ
BSD xargs
$ echo "a b c" | xargs -n1 a b c
GNU xargs
$ echo "a b c" | xargs -n1 a b c
異なる
BSD xargs
$ echo "a b c" | xargs -n1 -I {} echo {} a b c
GNU xargs
$ echo "a b c" | xargs -n1 -I {} echo {} a b c
このように異なるおかげで、a, b, c と1つずつ処理しているつもりが、GNU版では一度に処理されてしまって、意図しない動きになっていた。
macOS では動くのに CI で動かない状態だったので、気づくのにすこし時間がかかった。つらい。
man をみたところ、GNU版は、-I
を使うとセパレータが改行になるらしい。ヘー。
面倒くさくなったので、xargs を使わないように改修した。
gcloud コマンドを使って Datastore から BigQuery にシュッとする
特定の Datastore Kind を適当に BigQuery へ持っていきたいとき、gcloud コマンドを使うと便利だった。
gcloud コマンドのインストールはドキュメントのとおり。
Export
エクスポートする App Engine プロジェクトは mcz で、Datastore の Kind は User, UserActionLog とする。
まず GCS(Cloud Storage) にエクスポート先バケットをつくる。
プロジェクトとバケットのリージョンが異なると怒られたので、要注意。
ここでは mcz_export というバケットをつくったことにする。
エクスポートコマンドは次のようになる。
gcloud --project=mcz beta datastore export --kinds="User,UserActionLog" gs://mcz_export/backup20171215
beta コマンドだけどちゃんと動く。
これで mcz_export/backup20171215 に User, UserActionLog のデータがエクスポートされる。
Import
続いて BigQuery にインポートする。
インポート先の dataset は mcz_data とする。
インポートコマンドは次のようになる。
bq load --project_id=mcz --replace --source_format=DATASTORE_BACKUP mcz_data.User gs://mcz_export/backup20171215/all_namespaces/kind_User/all_namespaces_kind_User.export_metadata
--replace
はなくても良いが、つけないと BigQuery にテーブルが既に存在するときインポート失敗するようになる。
適当に試したところ、1つずつしかインポートできないようだった。
ググり力が足りないのかもしれない。
ただコマンドを見ればお分かりのとおり、フォーマットが決まっている。
なので、Kind名のところを随時変えてあげれば、まとめてシュッともっていける。
次のようなスクリプトを書くと便利。
#!/bin/bash BACKUP_NAME="backup"`date '+%Y%m%d'` KINDS="User,UserActionLog" gcloud --project=mcz beta datastore export --kinds="${KINDS}" gs://mcz_export/${BACKUP_NAME} KINDS=`echo $KINDS | tr ',' ' '` for kind in ${KINDS}; do echo "${kind} importing..." bq load --project_id=mcz --replace --source_format=DATASTORE_BACKUP mcz_data.${kind} gs://mcz_export/${BACKUP_NAME}/all_namespaces/kind_${kind}/all_namespaces_kind_${kind}.export_metadata done
もっとしっかりやるなら Cloud Dataflow というものがあるのでそれを使うと良いらしい。