Golang Cafe #41 まとめ gorpを試す。
2014/08/03に開催された「Golang Cafe #41」についてのまとめです。
かつてGolang Cafe #4ではGo言語の標準パッケージのみ(各DB用のドライバは別として)でデータベースへのアクセスを試してみました。
今回はGo言語のORMの1つであるgorpを使ってみてどれほど便利なのかを試してみました。
今回はPostgreSQLとSQLiteで確認してみましたが、以下の説明はSQLiteでの結果を中心にまとめておきます。
検証に使用したコードはmattnさんのコードをベースにしました。
準備
以下の2つのパッケージをインストールします。
$ go get github.com/coopernurse/gorp $ go get github.com/mattn/go-sqlite3
※mattn/go-sqlite3をインストールするにはgccが必要になるため、Windows環境の方は事前にgccをインストールしておく必要があります。(gccのインストールについては「Windows7 64bit版でGo言語のクロスコンパイルを試す」を参考にしてください)
検証
テーブルに対応した構造体を定義しておきます。
type Person struct { Id int32 Name sql.NullString Age sql.NullInt64 Sex sql.NullBool Height sql.NullFloat64 Birthday time.Time BString []byte BBigInt []byte }
項目の型ですが、bool、、float64、int64、string、[]byte、time.Time型、Null許容型としてNullBool,、NullFloat64,、NullInt64、NullString、nilにしか対応していません。(Go言語の標準がそうだったと思います)
またDB上ではnullとして扱えても、Go言語ではNull許容型(C#のNullable
因みに、このPerson型をjson.Marshalしても、NullString(ほか)の構造体まで展開されてしまうので気をつけなくてはいけません。
次に実際にデータベースにアクセスする処理を書いていきます。
func main() { db, err := sql.Open("sqlite3", "./foo.db") if err != nil { panic(err.Error()) } dbmap := gorp.DbMap{Db: db, Dialect: gorp.SqliteDialect{}} t := dbmap.AddTableWithName(Person{}, "person").SetKeys(true, "Id") t.ColMap("Id").Rename("id") t.ColMap("Name").Rename("name") t.ColMap("Age").Rename("age") t.ColMap("Sex").Rename("sex") t.ColMap("Height").Rename("height") t.ColMap("Birthday").Rename("birthday") t.ColMap("BString").Rename("bstring") t.ColMap("BBigInt").Rename("bbigint") dbmap.DropTables() err = dbmap.CreateTables() if err != nil { panic(err.Error()) } tx, _ := dbmap.Begin() for i := 0; i < 100; i++ { p := Person{} if rand.Float32() > 0.5 { p.Name.Scan(fmt.Sprintf("mattn%03d", i)) } if rand.Float32() > 0.5 { p.Age.Scan(i) } if rand.Float32() > 0.5 { p.Sex.Scan(rand.Float32() > 0.5) } if rand.Float32() > 0.5 { p.Height.Scan(rand.Float32()) } if rand.Float32() > 0.5 { p.Birthday = time.Now() } if rand.Float32() > 0.5 { p.BString = []byte(fmt.Sprintf("mattn%03d", i)) } if rand.Float32() > 0.5 { p.BBigInt = big.NewInt(int64(i * 10000)).Bytes() } err = tx.Insert(&p) if err != nil { tx.Rollback() panic(err.Error()) } } tx.Commit() list, _ := dbmap.Select(&Person{}, "select * from person") for _, l := range list { p := l.(*Person) var ( bstring string bbigint big.Int ) if p.BString != nil { bstring = string(p.BString) } if p.BBigInt != nil { bbigint = big.Int{} bbigint.SetBytes(p.BBigInt) } fmt.Printf("%d, %s, %d, %t, %f, %v, %d\n", p.Id, p.Name, p.Age, p.Sex, p.Height, bstring, bbigint) } }
簡単に流れを説明すると、まずgorp.DbMap型を作成します。作成するときに各データベース用のDialectを指定します。
dbmap.AddTableWithNameメソッドでテーブル名と対応する構造体を指定します。またSetKeysメソッドで主キーを指定します。
その後でTableMap.ColMapメソッドとColumnMap.Renameメソッドで構造体の項目とテーブルの項目を結びつけてあげます。
DbMap.DropTablesメソッドとDbMap.CreateTablesはそのままテーブルの削除と作成です。このサンプルではDbMap.DropTablesの戻り値は無視しているのですが、厳密にテーブルが存在している場合のみテーブルを削除する場合は、DbMap.DropTablesIfExistsメソッドを使用します。
DbMap.Begin、Transaction.Rollback、Transaction.Commitでトランザクションを使用することができます。ドキュメントを見る限りではセーブポイントも利用できそうです。
クエリーの発行については、Transactionのメソッドを実行するか、DbMapのメソッドを実行するかのいずれかになります。
実際の実行結果は以下のようになります。
1, {mattn000 %!s(bool=true)}, {0 %!d(bool=true)}, {false true}, {0.000000 %!f(bool=false)}, , {%!d(bool=false) []} 2, { %!s(bool=false)}, {0 %!d(bool=false)}, {true true}, {0.000000 %!f(bool=false)}, , {%!d(bool=false) []} 3, { %!s(bool=false)}, {0 %!d(bool=false)}, {false true}, {0.000000 %!f(bool=false)}, mattn002, {%!d(bool=false) [20000]} 4, { %!s(bool=false)}, {0 %!d(bool=false)}, {false true}, {0.696719 %!f(bool=true)}, , {%!d(bool=false) []} 5, {mattn004 %!s(bool=true)}, {4 %!d(bool=true)}, {false false}, {0.059121 %!f(bool=true)}, , {%!d(bool=false) []} ...
非常に見難いのはNullInt64のような型を使用しているためです。
GUIクライアントで確認してみるとこのようになっています。
確かに簡単にテーブルを作成してくれるのは嬉しいですが、DBの設計に拘る人にとっては思うようなテーブルを作成することは難しそうに思います。(できるのかもしれませんがそこまでは確かめていません)
それに関連してDecimal型を扱うのにはどうしたら良いのかというのが気になります。Go言語ではC#のようなdecimal型はなくmath/bigパッケージのInt型を使うしかありません。
不運なことにbig.Int型Go言語の標準あるいはDBのドライバでも対応してなさそなので、苦肉の策として
BOLB型として保存してみました。ただBLOB型では保存しているだけなのであまり意味がなさそうな気がします。
一日も早いdecimal型への対応が望まれます!
PostgreSQL編
ちなみにPostgreSQLを使用する場合は、以下のように2箇所を変更するだけで動作します。
db, err := sql.Open("postgres", "user=postgres password=postgres host=localhost dbname=godbtest sslmode=disable") dbmap := gorp.DbMap{Db: db, Dialect: gorp.PostgresDialect{}}
またpgドライバにはpq.NullTimeという型も用意されています。
Windows上のVMWareでCoreOS、Dockerを動かす
WindowsでDockerを動かすにはVagrant + VirtualBoxを使った方法が一般的なようですが、私は普段はVMWareを利用していて、その環境にVirtualBoxを同時にインストールして競合してしまいハマった経験があるので、VMWareとCoreOSを使ったシンプルな環境を構築した際のメモです。
最終的なゴールは、
Windows上のVMWareにCoreOSを導入し、Dockerfileを使用してPostgreSQLの動作するコンテナを作成、起動し、クライアント(pgAdminまたはアプリ)からアクセすることです。
準備
※VMWare、msysGitのインストール方法や使い方には触れませんので各自お調べください。
CoreOSのセットアップ
CoreOSはVMWare用のイメージがあるので、これをダウンロードしてきます。
https://coreos.com/にアクセスして、画面左側の「Lastest Release Info」をクリックします。
今回はStable版を使用するので、StableのBrowse Imageをクリックします。
ファイルの一覧が表示されるので、その中からcoreos_production_vmware_insecure.zipをダウンロードします。
適当なフォルダに展開し、coreos_production_vmware_insecure.vmxを起動すると数十秒で起動でログイン画面が表示されます。(デバイス構成の変更は適宜してください)
※以降はsshクライアントから操作します。
画面に表示されているIPアドレスを確認して、coreos_production_vmware_insecure.vmxが存在するディレクトリに鍵ファイルがあるので、これを使ってsshクライアントから接続します。
win7@ $ ssh -i insecure_ssh_key core@IPアドレス
CoreOSには既にDockerがインストールされているのでバージョンを確認してみます。
core@$ docker version Client version: 1.0.1 Client API version: 1.12 Go version (client): go1.2 Git commit (client): 990021a Server version: 1.0.1 Server API version: 1.12 Go version (server): go1.2 Git commit (server): 990021a
PostgreSQLコンテナの作成
今回はPostgreSQLが動作するコンテナを作成します。なぜPostgreSQLなのかというと、Golang Cafe #41でPostgreSQLの環境が欲しかったというだけです。
Dockerizing a PostgreSQL serviceを参考にしながらコンテナを作成していきます。
まずCoreOS上でDockerfileの配置場所を作成します。
core@ $ mkdir postgresql core@$ cd postgresql/
そのままCoreOS上でDockerfileを作成してもよいのですが、CoreOSに接続した状態では私のmsysGitのvimが正常に動作しないので、ホスト(Windows7)上で作成してコピーします。
win7@ $ scp -i insecure_ssh_key ./postgresql.Dockerfile core@IPアドレス:postgresql/Dockerfile
Dockerfileの中身はリンク先を参照してください。Dockerfileの中身についても今回は触れません。
再度CoreOSに接続してイメージをビルドします。
core@ $ mkdir postgresql core@ $ cd postgresql/ core@ $ ls Dockerfile core@ $ sudo docker build -t eg_postgresql .
しばらく時間がかかりますが、イメージが作成されます。
core@ $ dokcer images REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE eg_postgresql latest 45b811be258a 52 minutes ago 366.7 MB ubuntu trusty ba5877dc9bec 2 weeks ago 192.7 MB ubuntu latest ba5877dc9bec 2 weeks ago 192.7 MB ubuntu 14.04 ba5877dc9bec 2 weeks ago 192.7 MB ubuntu 12.04 b9e56c8f2cf5 2 weeks ago 103.8 MB ubuntu precise b9e56c8f2cf5 2 weeks ago 103.8 MB ubuntu utopic 6ef6f1a66de1 2 weeks ago 194.1 MB .....
作成したイメージからコンテナを起動します。デーモンとして動作させ、コンテナ上のポートを公開させるために-d、-Pオプションを使います。
core@ $ sudo docker run -d -P --name pg_test eg_postgresql
外部からコンテナ上のPostgreSQLにアクセスするためのポートを確認します。
core@ $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES c33476aa6397 eg_postgresql:latest /usr/lib/postgresql/ 12 seconds ago Up 9 seconds 0.0.0.0:49153->5432/tcp pg_test
pgAdmin等で接続を確認します。
無事に接続も確認できたのでこれで目標は達成です。
起動中のコンテナを停止します。
core@ $ docker stop pg_test
CoreOSの終了時は普通のlinuxと同じく
core@ $ sudo shutdown -h now
で数秒で終了します。
Dockerについての詳しい内容は適宜調べていただくか、下記のような書籍を参考にされるといいと思います。
Docker入門 Immutable Infrastructureを実現する
- 作者: 松原豊,米林正明
- 出版社/メーカー: 技術評論社
- 発売日: 2014/04/25
- メディア: Kindle版
- この商品を含むブログ (5件) を見る
まとめ
以前Golang Cafe #26〜でDockerのソースコードの一部は読みましたが、実際に試してみることはありませんでした。更にはちょっと怖いようなイメージもありました。
しかし、以前ソースコードを読んでいたせいか、触り始めるとほとんど抵抗はなくなりました。これもGolang Cafeの成果ですね。でもまだ若干ブラックボックスを触っている感じがあります。