Slickのドライバーを定義ファイルで指定する方法

Playで開発をしていて、テストや開発の序盤はとりあえずH2を使って、本番ではMySQLを使う場合、Slickのドライバーをimport scala.slick.driver.H2Driverのように直接指定してしまうと、後々すべてのモデルのソースを変更していかなければいけなくて、なんかいい方法無いかなと探してたらこんな記事を見つけたので、これを参考に自分なりに対応してみました。

※ サンプルコードをgithubにあげています。
Source code:yyhayashi303/play21-slick · GitHub


今回の環境は以下のとおり。

play-2.1.0
slick-1.0.0
scala-2.10.0

まずは、定義ファイルからドライバーのインスタンスを生成するトレイトを下記のように実装します。
各ドライバーはExtendedProfileを継承しているので、返されるインスタンスの型はExtendedProfileを指定します。

import play.api.Application
import slick.driver.ExtendedProfile

trait Profile {
  val SLICK_DRIVER = "slick.db.driver"
  val DEFAULT_SLICK_DRIVER = "scala.slick.driver.H2Driver"

  def getProfile(implicit app: Application): ExtendedProfile = {
    val driverClass = app.configuration.getString(SLICK_DRIVER).getOrElse(DEFAULT_SLICK_DRIVER)
    singleton[ExtendedProfile](driverClass)
  }

  private def singleton[T](name : String)(implicit man: Manifest[T]): T =
    Class.forName(name + "$").getField("MODULE$").get(man.runtimeClass).asInstanceOf[T]

}

次に、Tableクラスを継承したBaseTableクラスを定義したいのですが、そもそもTableクラスを使う時点でimport scala.slick.driver.H2.simple._としないといけないので、ひと工夫する必要があります。
先ほど作ったProfileトレイトを継承したBaseModelクラスを作成し、そのクラス内に入れ子にしたクラスとしてBaseTableクラスを定義します。

import play.api.Play.current
import play.api.db.DB
import slick.driver.ExtendedProfile

object BaseModel extends Profile {
  lazy val profile: ExtendedProfile = getProfile
  import profile.simple._

  def withSession[T](f: (Session => T)): T = Database.forDataSource(DB.getDataSource()).withSession(f)

  abstract class BaseTable[T](name: String) extends Table[T](name)
}

BaseModelの中でprofileを取得し、import profile.simple._とすることで、同じスコープ内にあるBaseTableが継承するTableクラスを解決することができます。

最後にこのBaseTableを継承してモデルクラスを作成します。

import common.BaseModel

case class User(id: Int, firstName: String, lastName: String, email: String)
object Users extends BaseModel.BaseTable[User]("USER") {
  import BaseModel.profile.simple._

  def id = column[Int]("id", O PrimaryKey, O AutoInc)
  def firstName = column[String]("first_name", O DBType "varchar(20)")
  def lastName = column[String]("last_name", O DBType "varchar(20)")
  def email = column[String]("email", O DBType "varchar(32)")
  def * = id ~ firstName ~ lastName ~ email <> (User.apply _, User.unapply _)
  def ins = firstName ~ lastName ~ email returning id
}

あとは、application.confにドライバーの定義を記述します。

# デフォルトはH2
slick.db.driver=scala.slick.driver.H2Driver

各環境用の定義ファイル(例えばdevel.conf)にはドライバーの定義を上書くよう記述します。

include "application.conf"
slick.db.driver=scala.slick.driver.MySQLDriver

そして、アプリケーションを起動する際に下記のように定義ファイルを指定することでドライバーを切り替えられるようになります。

play -Dconfig.file=conf/devel.conf ~run