SlickでJDBCドライバを実行時に切り替えるメモ(ver. 2.x)

**追記(2015/06/02): Slick ver 2.xで試したメモです。ver 3.xでは互換性のない変更があるため、同じコードは動作しません。**

とあるコードで見かけたので、自分自身のためにメモ。

Slick公式のコードサンプル : https://github.com/typesafehub/activator-slick-multidb

## やりたいこと

稼働時は PostgreSQL に接続するけど、単体テスト時は H2Database に接続してCRUDとかその上のサービスクラスの挙動をテスト記述したい時がある。

[Slickのサンプル](http://slick.typesafe.com/doc/2.0.2/gettingstarted.html#quick-introduction)にもあるように通常はデータベースを選んで、その上でテーブル設計を作る。以下はPostgreSQLの場合。

```scala
import scala.slick.driver.PostgresDriver.simple._

// データモデル
case class User(id: Int, name: String)

// テーブル定義
class UserTable(tag: Tag) extends Table[User](tag, "users") {
  def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
  def name = column[String]("name")
  def * = (id, name) <> (User.tupled, User.unapply) 
}

// クエリ実行オブジェクト
val Users = TableQuery[UserTable]
```

でも使う段になって、上記コードの中身は全くPostgreSQLに依存しているわけではないのに、`Table`や`TableQuery`が`PostgresDriver`によって生成されるものであるため、 `H2Driver` に切り替えることは出来ない。

```scala
import scala.slick.driver.H2Driver.simple._

Database.forURL("jdbc:h2:mem:test", driver = "org.h2.Driver") withSession { implicit session =>
  // 定義時とドライバが合わないのでコンパイルエラー
  Users.insert(User(-1, "sample")) 
}
```

Slick 2.x にはいくつかのProfileが設定されていてざっくりとは以下の様な階層になっているようだ。
上記でimportしているのはobjectに定義されたsimple以下を使っている。（ここに暗黙の型変換とか定義されてる）ちなみに、objectは同名のクラスをシングルトンインスタンスにしたもの。

```
JdbcProfile -> JdbcDriver(class) -> PostgresDriver(class), H2Driver(class), ...
                 JdbcDriver(object)   PostgresDriver(object)  H2Driver(object) ...            
```

じゃあ、 例えば上位クラスの `JdbcDriver` を定義時にセットして実行時に `PostgresDriver` を利用していいかというと、やはり同じく実行時のドライバ指定ができない。

そこで、実行時に依存性注入(DI)することになる。

## テーブル定義のComponent化

DIでは、「何らかの `JdbcProfile` には依存するが、それは実行時に決まる」という形で宣言する。

```Profile.scala
trait Profile {
  def profile: JdbcProfile
}
```

```UserTable.scala
trait UserTableComponent { this: Profile =>
  import profile.simple._

  // UserTableの定義を内包
}
```

このように定義しておくことで、どのProfileが使われるかは実行時に決めることができる。

```scala
trait H2Profile extends Profile {
  val profile = H2Driver
}

trait PostgresProfile extends Profile {
  val profile = PostgresDriver
}
```

そして、実行時に `UserTable` を使うクラスは、 `UserTableComponent` をmix-inして使うように宣言する。

```scala
class SomeService { this: UserTableComponent =>
  // UserTableの処理
}

// サービスクラスのインスタンス化
object SomeService extends SomeService with UserTableComponent with PostgresProfile
```

## DBコネクションのComponent化

上記の説明で足りない部分があり、それはずばりDBへの接続の部分。

- `Database.forURL` のように呼び出したくても、`SomeService`クラスでは実際には `Database` オブジェクトのメソッドは呼び出せない。(Profileを持ってないから)
- [Slickの説明](http://slick.typesafe.com/doc/2.0.2/connection.html#connection-pools)にもある通り、コネクションプーリングは機能に含まれていないので、コネクションプーリングライブラリから生成された `DataSource` を使うようにする。
- でも、テスト時はコネクションプーリングは要らない。

ということで、 `Database` のインスタンスを提供するコンポーネントが別に必要になる。
例えば、ドライバのクラス名を提供できるようにすればシンプルになる、などの工夫は必要かと思います。

```Profile.scala
trait Profile {
  def profile: JdbcProfile
  def driverClassName: String
}
```

```Database.scala
trait DatabaseComponent { this: Profile =>
  def database: profile.simple.Database
}

// テスト時
trait H2DatabaseComponent extends DatabaseComponent {
  import profile.simple._

  def database = Database.forURL("jdbc:h2:mem:test")
}

// 稼働時など
trait DbcpBasicDatabaseComponent extends DatabaseComponent {
  import profile.simple._
　import org.apache.commons.dbcp2.BasicDataSource

  def database = {
    val ds = new BasicDataSource
    ds.setDriverClassName(driverClassName)
    // ...
    Database.forDataSource(ds)
  }
}
```

あえて省略していますが、実際にはdatabaseメソッドに引数を設定するか、別のConfigをmix-inするなどして接続先情報を定義する必要があります。

## DI, DI, またDI

ということで、結果的に以下のものを実際のサービスから分離して、実行時にmix-inすることになります。

- `JdbcProfile`: Slickで定義された各種RDBMS用のドライバ（DDLとかクエリの差分とかよしなに生成してくれる）
- コネクション: 普通に接続するとか、DBCPやBoneCPなどでプーリングしたものから取得するとか。
- 接続先情報: これはそもそも実行時にしか決まらないもの。設定情報を提供する `Config` などのコンポーネントを使うことになる。

実行するためには絡めなきゃいけない情報でも、DIでうまく分離することで一つ一つの役割を明確にしたり、あるいはモックで置き換えることができるようになるので便利です。

欠点としてはこれらの仕組みを理解するのに時間がかかることと、クラスが増えてしまうことくらいでしょうか。