1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

PlayのBuiltInComponentsのクラス階層について

Posted at

Playのコンパイル時DIで用意するBuiltInComponetnsのクラス階層について整理します。

基礎

まずはconfに指定したApplicationLoader実装クラス(図ではMyApplicationLoader)のloadメソッドから始まります。このメソッドの中でBuiltInComponents実装クラス(図ではApplicationComponentsFromContext)を使ってApplicationインスタンスを生成します。

MyApplication.scala
import play.api.Application
import play.api.ApplicationLoader
import play.api.ApplicationLoader.Context

class MyApplicationLoader extends ApplicationLoader {
  override def load(context: Context): Application = {
    new ApplicationComponentsFromContext(context).application
  }
}

ApplicationComponentsFromContextBuiltInComponentsを実装します。BuiltInComponentsの抽象メンバのうち、Contextから導出できるものを定義したヘルパークラスがBuiltInComponentsFromContextです。

ApplicationComponentsFromContext.scala
import com.github.mumoshu.play2.memcached.api.MemcachedComponents
import play.api.ApplicationLoader.Context
import play.api.BuiltInComponentsFromContext

class ApplicationComponentsFromContext(context: Context)
    extends BuiltInComponentsFromContext(context: Context)
    with ApplicationComponents
    with MemcachedComponents

メインとなるコンポーネント時DIはApplicationComponentsが担います。

ApplicationComponents.scala
trait ApplicationComponents
    extends BuiltInComponents
    with ... /* 他たくさん */ {

  // playはルーターインスタンスを作りさえすれば動く。SIRDを使えば、routesファイルやコントローラーすらなくてもよい。
  override lazy val router: Router = ...
}

テストを考慮した設計

例えば、本番環境ではmemcachedを使い、単体テスト実行時はcaffeineを使う場合、これらのコンポーネントを一緒に使うことはできません。コンポーネントはケーキパターンを想定しているため、同じキャッシュAPIの異なる実装を混ぜることができないのです。

ApplicationComponents.scala
trait ApplicationComponents
  extends BuiltInComponents
  with MemcachedComponents
  with CaffeineCacheComponents
  ... {

  // この場合 defaultCacheApi を使うと、2つのうち、どれを使えばいいかが決定できない。
  // コンポーネントの実装メンバはlazy valなので、superも使えない。

}

そこで、これらを本番とテストで切り替えられるようにクラス構成を考えます。答えとしては既に図で示した通りで、本番ではApplicationComponentsFromContextにてmemcachedを実装し、テストではFakeApplicationComponentsにてcaffeineを実装します。FakeApplicationComponentsは本番でも利用するApplicationComponentsを継承しているため、キャッシュAPI以外のワイヤリングを再利用できます。やったね。

FakeApplicationComponents.scala
class FakeApplicationComponents(context: Context)
    extends BuiltInComponentsFromContext(context: Context)
    with ApplicationComponents
    with CaffeineCacheComponents
    with ... {
  ...
}

ちなみに、このFakeApplicationComponentsはこんな感じで使います。

OneMyAppPerSuite.scala
trait OneMyAppPerSuite extends OneAppPerSuiteWithComponents {
  self: TestSuite =>

  // デフォルトのフェイクコンポーネントを返す
  override def components: ApplicationComponents = {
    new FakeApplicationComponents(context)
  }
}
ControllerSpec.scala
class ClientControllerSpec extends AnyFunSpec with OneMyAppPerSuite {
  describe("/hello") {
    it("コンテンツに「hello」が含まれること") {
      // ここでアプリが起動している
      val path = "/hello"
      val res = route(app, FakeRequest(GET, path)).get
      val contents = contentAsString(res)
      assert(contents.contains("hello"))
    }
  }
}
1
1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?