GAE/Go で Google Cloud Spanner を操作する（前編）

こんにちは。今日はネコの日（2017 年 2 月 22 日）ですね :cat:

昨年（2016）の 10 月ぐらいから Go 言語を勉強し始めた @wezardnet です。新しい言語はまだまだ慣れませんｗ
さて、今回は Go 言語の学習がてら先日ローンチされた [Google Cloud Spanner](https://cloud.google.com/spanner/) を触ってみようと思います。具体的には Go で Cloud Spanner のインスタンスを作成／削除したり、データベースを作ったりをしてみたので、忘れないように書いておこうと思います。タイトルに「（前編）」と入れましたが、後編があるかどうかは未定です :sweat_smile:

#1. Cloud Spanner ってなに？
ネットなどのニュース記事を読むと、要はスケールアウトするリレーショナルデータベースって感じでしょうか。グーグルのコアサービスでも使われているそうです。現在はベータ版で Google Cloud Platform(GCP) のプロジェクトを作成すれば誰でも使えるようになっています。

* [Googleがグローバルな分散データベースCloud Spannerをローンチ、SQLとNoSQLの‘良いとこ取り’を実装](http://jp.techcrunch.com/2017/02/15/20170214google-launches-cloud-spanner-a-new-globally-distributed-database-service/)
* [グーグルがRDBとNoSQLの「いいとこ取り」--DBサービス「Cloud Spanner」発表](https://japan.zdnet.com/article/35096603/)
* [グーグル新発表の「Google Cloud Spanner」はミッションクリティカルな業務に使える？](http://www.sbbit.jp/article/cont1/33289)
* [「Google Cloud Spanner」発表。地球規模の大規模分散環境で稼働するミッションクリティカルなリレーショナルDB。NoSQL並のスケーラビリティでSQL対応、トランザクション処理を実現](http://www.publickey1.jp/blog/17/google_cloud_spannerdbnosqlsql.html)


#2. Cloud Spanner を使うための準備
まずは [Google Developer Console](https://console.cloud.google.com/) の API Manager で Cloud Spanner API を有効にします。

![API Library.png](https://qiita-image-store.s3.amazonaws.com/0/12023/320ff060-112b-9db9-c050-8912791df0d9.png)

次に Cloud Spanner API 用に Service Account を作成し JSON キーファイルを入手します。

![ServiceAccount.png](https://qiita-image-store.s3.amazonaws.com/0/12023/5ea1484b-2c3f-861b-9797-2c5a1a43d8f5.png)

以上で準備は完了です :v:


#3. コードを書く
それでは実際にコードを書いていきます。インスタンス操作とデータベース操作に分けて解説します。コードは見づらくなるので一部省略してます。

##3.1 インスタンスの作成と削除

必要なパッケージをインポートします。

```Go:Import
package spanner

import (
	"cloud.google.com/go/spanner"
	"cloud.google.com/go/spanner/admin/database/apiv1"
	"cloud.google.com/go/spanner/admin/instance/apiv1"

	"golang.org/x/oauth2"
	"golang.org/x/oauth2/google"

	"google.golang.org/api/option"
	"google.golang.org/appengine"
	"google.golang.org/appengine/log"

	admindb "google.golang.org/genproto/googleapis/spanner/admin/database/v1"
	adminis "google.golang.org/genproto/googleapis/spanner/admin/instance/v1"
)
```

JSON キーファイルを読み込んでアクセストークンを取得し Admin Client を作成します。

```Go:CreateAdminClient
ctx := appengine.NewContext(r)

// Service Account でアクセスするための JSON ファイルを読み込む
data, err := ioutil.ReadFile("{JSON Key File}")
if err != nil {
	log.Errorf(ctx, "err = %v", err.Error())
	return
}

conf, err := google.JWTConfigFromJSON(data, "https://www.googleapis.com/auth/spanner.admin", "https://www.googleapis.com/auth/spanner.data")
if err != nil {
	log.Errorf(ctx, "err = %v", err.Error())
	return
}
option := option.WithTokenSource(conf.TokenSource(ctx))

adminClient, err := instance.NewInstanceAdminClient(ctx, option)
if err != nil {
	log.Errorf(ctx, "Error NewInstanceAdminClient: %v", err)
	return
}
```

「JSON キーファイルを読み込んで...」という部分は App Engine のサービスアカウントを使うと次のように 1 行で済みます（ご指摘ありがとうございます！） :smiley:

```Go
// App Engine Service Account のトークンをセットする
option := option.WithTokenSource(google.AppEngineTokenSource(ctx, "https://www.googleapis.com/auth/spanner.admin", "https://www.googleapis.com/auth/spanner.data"))

```

Cloud Spanner のインスタンスを作成するコードは次のようになります。この例ではリージョンは「asia-east1」、ノード数は「1」としています。

```Go:CreateInstance
op, err := adminClient.CreateInstance(ctx, &adminis.CreateInstanceRequest{
	Parent:     "projects/{Youre Project ID}", 
	InstanceId: "{Youre Instance ID}", 
	Instance:   &adminis.Instance{
		Config:      "projects/{Youre Project ID}/instanceConfigs/regional-asia-east1", 
		DisplayName: "{Youre Instance Display Name}", 
		Name:        "projects/{Youre Project ID}/instances/{Youre Instance ID}", 
		NodeCount:   1, 
	}, 
})
if err != nil {
	log.Errorf(ctx, "Error Create Instace: %v", err)
	return
}
if _, err := op.Wait(ctx); err == nil {
	// インスタンス作成が成功
}
```

Cloud Spanner はインスタンスを上げてるだけでも課金されるので不要になったら削除しないと大変です。今回の記事を書くために試行しただけで自身のプロジェクトの課金状況を見たら $10.8 課金されてました :worried:

```Go:DeleteInstance
adminClient.DeleteInstance(ctx, &adminis.DeleteInstanceRequest{
	Name: "projects/{Youre Project ID}/instances/{Youre Instance ID}", 
})
```


##3.2 データベースとスキーマの作成
データベースの作成もインスタンス作成とほぼ同じ実装になります。まずは JSON キーファイルを読み込んでアクセストークンを取得し Admin Client を作成します。

```Go:CreateAdminClient
ctx := appengine.NewContext(r)

// Service Account でアクセスするための JSON ファイルを読み込む
data, err := ioutil.ReadFile("{JSON Key File}")
if err != nil {
	log.Errorf(ctx, "err = %v", err.Error())
	return
}

conf, err := google.JWTConfigFromJSON(data, "https://www.googleapis.com/auth/spanner.admin", "https://www.googleapis.com/auth/spanner.data")
if err != nil {
	log.Errorf(ctx, "err = %v", err.Error())
	return
}
option := option.WithTokenSource(conf.TokenSource(ctx))

adminClient, err := database.NewDatabaseAdminClient(ctx, option)
if err != nil {
	log.Errorf(ctx, "Error NewDatabaseAdminClient: %v", err)
	return
}
```

今回はサンプルのため、次のような昔ながらの得意先マスタを作成してみることにします。

| 論理名 | 物理名 | データ型 | Not Null |
|:-------------:|:------------:|:------------:|:------------:|
|得意先コード|TCODE|number(10)|Yes|
|フリガナ|FURIGANA|varchar2(50)||
|得意先名|TNAME|varchar2(50)||
|郵便番号|YUBIN|varchar2(8)||
|住所１|ADD1|varchar2(50)||
|住所２|ADD2|varchar2(50)||
|電話番号|TEL|char(12)||
|FAX番号|FAX|char(12)||
|担当者名|TANTOU|varchar2(20)||
|値引率|NEBIKI|number(19,3)||
|メールアドレス|MAILADDR|varchar2(50)||

データ型は Cloud Spanner で用意されているモノに置き換えます。データ型については [こちら](https://cloud.google.com/spanner/docs/data-types) を参照してください。

```Go:CreateDatabase
op, err := adminClient.CreateDatabase(ctx, &admindb.CreateDatabaseRequest{
	Parent:          "projects/{Youre Project ID}", 
	CreateStatement: "CREATE DATABASE {Youre Database Name}", 
	ExtraStatements: []string{
		`CREATE TABLE TOKUI (
			TCODE     INT64 NOT NULL, 
			FURIGANA  STRING(50), 
			TNAME     STRING(50), 
			YUBIN     STRING(8), 
			ADD1      STRING(50), 
			ADD2      STRING(50), 
			TEL       STRING(12), 
			FAX       STRING(12), 
			TANTOU    STRING(20), 
			NEBIKI    FLOAT64, 
			MAILADDR  STRING(50), 
		) PRIMARY KEY (TCODE)`, 
	}, 
})
if err != nil {
	log.Errorf(ctx, "Error Create Database: %v", err)
	return
}
if _, err := op.Wait(ctx); err == nil {
	// データベース & スキーマ作成が成功
}
```


#4. 管理コンソールで確認する
実際にインスタンスやデータベースが作られているかどうか  [Google Developer Console](https://console.cloud.google.com/) の Cloud Spanner で確認してみます。

![Database.png](https://qiita-image-store.s3.amazonaws.com/0/12023/5b6b7e28-a6ad-9e83-1257-f5accb048fb0.png)

いい感じに出来てますね :smiley:


#5. おわりに
実際に実行してみると、インスタンスはサクッと作られますね。
今回は Cloud Spanner の導入部分だけを試しただけで、その本領域を知るに至っていません。次回（モチベがあれば）は RDBMS のデータを移行できるかどうか、トランザクションはどうなのか、SQL のような扱いが可能か、といった点を調査してみたいと思います。。。