Slick3用のブロッキングAPIを作ってみました

github.com

なぜ作ったのか?

事の発端はSlickのこのイシューです。

github.com

GitBucketはServletベースということもあり、Slick3のDBIOは非同期実行のメリットが得られないのに複雑さだけが劇的に向上してしまうこと、プラグイン開発者にもモナディックなプログラミングを強いてしまうことなどから当面Slick3へのバージョンアップは行わず、Slick2に留まることにしていました。

しかし、Slickは場合によっては以下のような酷いSQLを生成することがあります。Slick3にはクエリコンパイラが改善されている(今後の改善も期待できる)という利点があるため、可能であるならバージョンアップしたいというのが正直なところでした。

github.com

そこで冒頭のようなイシューが上がっていたため、これはコントリビュートのチャンス、もしSlick3でSlick2互換のブロッキングAPIが利用できるようになればGitBucketをほぼ無傷でSlick3に移行でき、クエリコンパイラの改善というメリットを享受することができるという期待がありました。

というわけでプロトタイプも作って提案してみたのですが、自分のコミュ力不足もあってかあまり理解が得られず、埒が明かなそうだったので独自のライブラリとして提供することにしました。

使い方

build.sbtに以下の依存関係を追加します。

libraryDependencies += "com.github.takezoe" %% "blocking-slick" % "0.0.1"

使っているデータベースに合わせて以下のようなオブジェクトを定義します。

package myapp.slick.driver

import slick.driver.H2Driver
import com.github.takezoe.slick.blocking.SlickBlockingAPI

object BlockingH2Driver extends H2Driver with SlickBlockingAPI

このオブジェクトを以下のようにインポートするとブロッキングAPIが使えるようになります。

import myapp.slick.driver.BlockingH2Driver._
import myapp.slick.driver.BlockingH2Driver.api._

以下はブロッキングAPIの使用例です。

val db = Database.forURL("jdbc:h2:mem:test")

db.withSession { implicit session =>
  // Create tables
  models.Tables.schema.create

  // Insert
  Users.unsafeInsert(UsersRow(1, "takezoe"))

  // Select
  val users: Seq[UserRow] = Users.list

  // Select single record
  val user: UserRow = Users.filter(_.id === "takezoe".bind).first

  // Select single record with Option
  val user: Option[UserRow] = Users.filter(_.id === "takezoe".bind).firstOption

  // Update
  Users.filter(t => t.id === 1.bind).unsafeUpdate(UsersRow(1, "naoki"))

  // Delete
  Users.filter(t => t.id === 1.bind).unsafeDelete

  // Drop tables
  models.Tables.schema.remove
}

トランザクションが必要な場合はwithSessionの代わりにwithTransactionを使用します。

// Transaction
db.withTransaction { implicit session =>
  ...
}

以下にplay-slickと組み合わせてPlay2アプリケーションでこのブロッキングAPIを使った場合のサンプルもあります。

github.com

今後のScalaのORM事情

SlickはTypesafe社のプロジェクトだから使っていたというユーザも多いのではないかと思いますが、Lightbendのスタックから外れてしまった今となっては使い続けるべきなのか微妙なところではあります。クエリコンパイラも3.x系で改善されているとはいえまだ厳しいSQLになってしまうケースもあるみたいですし…。

ただ、今だと代替品に何を使うかというのが悩ましいところで、少し前から注目しているquillはマクロによる実装なので将来的にscalametaにちゃんと移行できるのか?という不安があります。ScalikeJDBCは学習コストが低く、生成されるSQLも素直なのでリスクが少ないと言えますが、国外での知名度という面でやや不安が残るところです。

そもそも海外ではSlickの厳しいSQLが問題になるようなRDBの使い方はレアケースなのか、Scalaに限らずRDB周りは昔から国内と海外の温度差を感じることが多いのですが、実際のところどうなのかというのは個人的には気になるところです。