LoginSignup
30
30

More than 5 years have passed since last update.

ScalaのDIにMacWireを使う

Posted at

ScalaでのDIはいくつもやり方があります。CakePatternを使った方法や、ImplicitParameterやReaderMonadを使った方法などです。javaっぽくGoogleGuiceみたいなDIコンテナを使うこともできます。

今回は数あるScalaのDIライブラリの中でも、コンストラクタDIを簡単に書けるMacWireというものを紹介します。

MacWireとは

詳細な説明はドキュメントがあります。
簡単に言うと、

  • コンストラクタDI最高。
  • コンパイル時に依存はチェックしたい。
  • 依存関係書くの辛いからマクロで解決。

という感じで、マクロでDIの依存関係を解決してくれます。

MacWireなしのコンストラクタDIの例

MacWireなし
trait DbModule {
  lazy val db = new DB("localhost", 3306)
  lazy val userDao = new UserDao(db)
}

MacWireありのコンストラクタDIの例

MacWireあり
import com.softwaremill.macwire._

trait DbModule {
  lazy val db = new DB("localhost", 3306)
  lazy val userDao = wire[UserDao]
}

テストの時は同じですが、↑のようにwireを使うことで依存を書く必要がなくなります。

実際はマクロでnew UserDao(db)に展開されます。

使うときはmix-inして使えばokですね。

テスト用に、mockにしたModuleにしてみます。

Mockに
trait DbModuleMock {
  override lazy val db = mock[DB]
}

そしてテストでもmix-inしたりして使えばいいですね。

class UserDaoSpec extends DbModuleMock {
  "UserDao" should {
    "insert" it {
       userDao.insert(1)
       verify(db).insert(1) // mockになってるので呼ばれたかを確認してる
    }
  }
}

ここまでが簡単な例でした。

少し複雑な依存の例

依存が増えた時に、このwireはとても便利なものになります。

MacWireなし
trait DbModule {
  lazy val db = new DB("localhost", 3306)
  lazy val userDao = new UserDao(db)
  lazy val userIdGen = new UserIdGen()
  lazy val kvs = new Kvs("localhost", 6379)
  lazy val userCache = new UserCache(kvs)
  lazy val userService = new UserService(userIdGen, userCache, userDao)
}

dbのアクセスをkvs経由にしてcacheするみたいなイメージです。

これはwireで書くと

MacWireあり
trait DbModule {
  lazy val db = new DB("localhost", 3306)
  lazy val userDao = wire[UserDao]
  lazy val userIdGen = wire[UserIdGen]
  lazy val kvs = new Kvs("localhost", 6379)
  lazy val userCache = wire[UserCache]
  lazy val userService = wire[UserService]
}

とてもシンプルで良いですね。

テスト時はmockにしたModuleに。

MacWireあり。テスト時
trait DbModuleTest extends DbModule {
  override lazy val db = mock[DB]
  override lazy val kvs = mock[Kvs]
}

このようにシンプルなので学習コストはないに等しく、簡単にコンストラクタDIができ最高ですね。

テストの時だけFutureを同期実行する

テストの時だけFutureは同期的にしたいことは良くあります。やり方は、overrideや抽象的なFuture等いくつかあると思います。

DIを使ってテストの時だけ同期実行にしてみます。

まず非同期に実行するためのクラスを定義します。

Async
class Async {
  def apply[A](f: => A)(implicit ec: ExecutionContext): Future[A] = Future(f)
}

それをDIして使います。

UserService
import ExecutionContext...

class UserService(userDao: UserDao, async: Async) {
  def insert(name: String): Future[Long] = async {
    userDao.insert(name)
  }
}
DbModule
trait DbModule {
  lazy val db = new DB("localhost", 3306)
  lazy val async = wire[Async]
  lazy val userDao = wire[UserDao]
  lazy val userService = wire[UserService]
}

テストの時は同期的にします。

DbModuleTest
trait DbModuleTest extends DbModule {
  override lazy val db = mock[DB]
  override lazy val async = new Async {
    override def apply(f) = Future.successul(f) // テスト時だけ同期的にする
  }
}

こうすれば、asyncを使うように統一することでテスト時は全部同期実行にできます。

おまけ

下のほうに載ってますが、Tagged typeに対応したり、Playでの使用例が書いてあったり、Implicitでもwireできたりしますね。

いくつかサンプルプロジェクトあったりもして、ドキュメントが充実してて良いですね。

おわり(´ο`)=3

30
30
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
30
30