golang + mysql の ORM を色々調べた感想とおすすめ

普段は GAE で golang を使っているけど、
golangmysql 使ったことないってのもどうなのかな? と思ったので、
ちょっとしたサンプルアプリを通して mysql を使ってみようと思った。
で、ORM どーしーよーかなと思って、
色々調べた記録です。

標準パッケージ

標準パッケージが良い感じであれば、ORM は不要だと思ったので確認してみた。
標準パッケージでは database/sql を利用する。
https://golang.org/pkg/database/sql/

以下が使用例
https://github.com/golang/go/blob/master/src/database/sql/example_test.go

生のSQLを実行するのは問題ないんだけど、
Scan() で値を取り出していくのが面倒。

以下のように結果が複数レコードの場合に for で回すのも面倒。
https://github.com/golang/go/blob/master/src/database/sql/example_test.go#L77-L86

あと、struct とのマッピングは欲しい。

ということで、要件にもよるけど、
database/sql をそのまま利用するのはちょっと厳しいかな。

ちなみに、database/sql と database/sql を利用している ORM を利用するには
以下のドライバが必要になる。
https://github.com/golang/go/wiki/SQLDrivers

ドライバとdatabase/sqlの関係性は以下を読むとイメージできるかもしれない。
http://pospome.hatenablog.com/entry/2017/01/29/171904


ORMに求める要件

ORMを探すにあたって、個人的に以下の要件は必須。

structにマッピングできる

これは欲しい。
struct の tag とかでカラム指定したりするイメージ。

テーブル名とかstructに変な命名規則を適用させる必要がない

usersテーブルに対する struct は Users にする必要がある的なルールがあってもいーんだけど、
それを struct の tag とか、interface とかで回避できる仕組みは欲しい。

SQLが書ける

複雑なSQLをクエリビルダとかで書くのが面倒だったり、
ORMがSQLの方言に対応してなかった時(mysql5.7のjson系の関数とか)に生SQLで書きたい。
その結果はもちろん struct にマッピングしたい。



以下は必須じゃないけど、気にするところ。

発行されるクエリがイメージできる

「裏側で妙なSQLが発行されてインデックスが効かない」
「意図しないSQLが発行されている」
みたいなORMあるあるは回避したい。
association みたいなテーブルを良い感じに join する機能を明示的に利用しない限りこーゆーことはないと思うから、
そんなに気にする必要もないかもしれない。

パフォーマンスが極端に悪くない

早いに越したことはないが、どこまで速度にこだわるのかって問題になるから、
正直どーでもいいというか、使い勝手とのトレードオフ
どーしてもパフォーマンスが気になるなら、標準パッケージの database/sql 使えばいいと思うし、
どーでもいいなら、使い勝手を重視すればいい。



色々調べたが、これらを満たさないORMは基本なかったと思う。
なので、最終的には好みの問題になるのかもしれない。

ただ、パフォーマンスは実際に測定してないので分からない。
ネットに比較結果が転がっていたりするので、
気になるようであれば調べてみるといいかもしれない。

gorm

最初に調べたのが gorm というORMだった。
https://github.com/jinzhu/gorm

ドキュメントは以下。
http://jinzhu.me/gorm/

他のORMに比べて多機能な印象を持った。
associations とかもある。
http://jinzhu.me/gorm/associations.html

これを使っておけば変に困ることもないと思う。

ちなみに、gorm を fork した ngorm というのがあるが、
現時点で mysql に対応していないので見送り。
https://github.com/ngorm/ngorm

gorp

次は gorp を調べた。
https://github.com/go-gorp/gorp

「SELECTでは生SQLを書く必要がある」という点は独特だなと思った。

以下のようなイメージで、生SQLを書いて、それに struct をバインドする。
https://github.com/go-gorp/gorp/blob/master/gorp_test.go#L859

insert, update, delete は他のORMと同じ印象。
insert
https://github.com/go-gorp/gorp/blob/master/gorp_test.go#L432
update
https://github.com/go-gorp/gorp/blob/master/gorp_test.go#L564
delete
https://github.com/go-gorp/gorp/blob/master/gorp_test.go#L653

もう1点独特だなと思ったのは struct と DBテーブルのヒモ付を struct の tag, interface ではなく、
以下のようにコードで指定するところ。
struct が tag, interface で汚れないのは嬉しい。
https://github.com/go-gorp/gorp/blob/master/gorp_test.go#L1227-L1228

問題は SELECT WHERE IN() が使えない点。

使えないことはないけど、
gorpではSELECTをSQLで書く必要があるので、
以下のようにプレースホルダーを利用したSQL
そのプレースホルダーに対応する 100, 200, 300 という値をバインドしていかないといけない。

err = dbmap.Select(&users, "select * from posts where id in (?, ?, ?)", 100, 200, 300)



そして、以下のように slice で指定できないのがキツイ。
slice を指定するとエラーになる。

err = dbmap.Select(&users, "select * from posts where id in (?, ?, ?)", []int{100,200,300})



これどーやって in で指定するパラメータを可変にするんでしょうね・・・。
issue にもなっているが、解決されていない・・・。
https://github.com/go-gorp/gorp/issues/85
自分はここが気になって gorp の利用を見送った。

*この記事のコメントにて slice を指定できることを教えてもらいました。

dbr

https://github.com/gocraft/dbr

これは以下の記事を見た方が早い。
https://eurie.co.jp/blog/engineering/2015/12/go-lang-ormapper-dbr

自分の要件を満たしていて良い感じだったが、なぜか timezone が UTC から JST に変更できなかった。

dbrというか、database/sql で利用するドライバのレイヤの話になると思うんだけど、
以下のように設定すれば timezone を変更可能なはずが、なぜか反映されない・・・。
https://note.mu/tomyhero/n/nc31c788bc7d8
http://kenzo0107.hatenablog.com/entry/2015/08/19/165310

他の ORM と比較して公式のドキュメントが弱い印象も受けた。
というか、他が頑張ってドキュメント作ってる気がする。

JST問題は頑張って解決してもよかったが、一旦他のORMを調べることにした。

xorm

最後に試したのがこれ。
https://github.com/go-xorm/xorm

ドキュメントは以下。
http://xorm.io/docs/

エウレカさんで採用されているらしい。
https://developers.eure.jp/tech/go_web_application_1/

インメモリキャッシュを備えているのも珍しい。
実用に耐えるかは不明。

面白いと思ったのは
xormを利用すると wizard というシャーディング&レプリケーション用のライブラリも利用できる。
https://github.com/evalphobia/wizard
ちゃんと見てないし、使ってないから分からないんだけど、
生のSQLやクエリビルダは書けないっぽい????
https://github.com/evalphobia/wizard/blob/master/orm/xorm/interface.go#L13-L54

wizard に関しては、実際どの程度実用に耐えるのか不明。


この記事を書いた後に知ったORM

この記事を書いた後に知ったORMも載せておく。
実際に使ったわけじゃないので、パッ見た感じの感想のみ。

sqlboiler

以下の記事で知った。
https://qiita.com/gougyan/items/5295e4a30697a73868b5

sqlboiler
https://github.com/volatiletech/sqlboiler

以下によると、
rubyActiveRecord の生産性の高さを求めて作られたものっぽい。
https://github.com/volatiletech/sqlboiler#why-another-orm

そのため、
SQL のテーブル定義からモデルを自動生成するのが前提になっており、
そのモデルは ActiveRecord パターンを踏襲した操作感を提供している。
https://github.com/volatiletech/sqlboiler#find
https://github.com/volatiletech/sqlboiler#insert

おすすめは?

おすすめとしては gorm かな・・・。
パフォーマンス面や使い勝手(これは個人差あるけど)は他の ORM に劣るかもしれないが、
多機能なので「あれができない」「これができない」で困ることはないし・・・。
gorm が合わなかったら、他の ORM を使ってみればいいと思う。


結局何を使ったのか?

なんとなく最後の xorm を使うことにした。
今のところ、大きな問題もなく使えている。
素直に gorm にしておけばよかったのかもしれない・・・。

そーいえば、PHP の Cake, Fuel, rubyrails のようなフルスタックなWAFは
それ用のORMが付属してるから、
こーやって色々調べる必要なくて楽だったな・・・。