LoginSignup
11
9

More than 5 years have passed since last update.

PlayにおけるDependency Injection(DI)

Last updated at Posted at 2018-08-17

Dependency Injection

他のウェブフレームワークと同様にPlayでもDIができるようになっています。DIというのは、JSR330で規定されている依存性の注入の手法であり、Play Framework(2.4以降?)では、GoogleのGuiceというライブラリを使って実現されています。そんなに難しいわけではないのですが、原理とやり方をきちんと理解しないとなかなかとっつきにくい印象があります。AkkaのActorと組み合わせた例で説明したいと思います。

Play FrameworkにおけるDI

まず、JSR330を使うということは、javax.injectパッケージのアノテーションを使うということだということを理解する必要があります。何かのコンポーネントに対して依存性の注入を行うためには、@Injectアノテーションを使ってクラスを修飾します。ちなみに、scala(Play)の場合はコンストラクタで宣言するのが一般的のようです。

上述のように、Play FrameworkにはGuiceによるDIがあらかじめ組み込まれています。したがって、追加のライブラリなどをbuild.sbtに追加する必要はありません。このへんを参考に新しいPlayのプロジェクトを作ると、テンプレートのHomeContorllerにも@Injectアノテーションが使われていることがわかります。ここに、ActorをDIで組み込んでいきます。

app/controllers/HomeController.scala
@Singleton
class HomeController @Inject()(cc: ControllerComponents) extends AbstractController(cc) {

  def index() = Action { implicit request: Request[AnyContent] =>
    Ok(views.html.index())
  }

  def login() = Action {
    Ok(views.html.index())    
  }
}

DIするActorの準備

まず適当なActorを作ります。HelloActorという名前のActorをコンパニオンオブジェクトで宣言して、クラス内でメッセージが送られた際に呼ばれるrecieveというメソッドを定義しています。ちなみにPlayの場合は、HTTPによる応答が必要なので、メッセージを送りっぱなしというのは少なくて、何かしらsenderに返すという実装をするのが一般的です。一応、Typesafe Configでコンフィグを取ってくるようにしています。

app/actors/HelloActor.scala
package actors

import akka.actor._
import javax.inject._
import play.api.Configuration

object HelloActor {
  def props = Props[HelloActor]

  case class SayHello(name: String)
}

class HelloActor @Inject() (configuration: Configuration) extends Actor {
  import HelloActor._

  val config = configuration.getString("foo.bar").getOrElse("None")

  def receive = {
    case SayHello(name: String) =>
      sender() ! "Hello, " + config + "," + name
  }
}

このActorをDIで使えるようにするために、DIのモジュールを定義します。一般的なGuiceのやり方(AbstractModuleを拡張する)に従えばよいのですが、PlayのActor用にtraitが用意されているので、これを使います。

app/actors/MyModule.scala
package actors

import com.google.inject.AbstractModule
import play.api.libs.concurrent.AkkaGuiceSupport

import actors.HelloActor

class MyModule extends AbstractModule with AkkaGuiceSupport {
  override def configure = {
    bindActor[HelloActor]("configured-actor")
  }
}

MyModuleは"configured-actor"という名前でHelloActorをバインドするモジュールです。
このモジュールによって、DIでHelloActorを使うことができるようになります。
ただし、Playでは`application.conf'に使用するモジュールを追加宣言する必要があります。

application.conf
play.modules.enabled += "modules.GuiceModule"
foo = {"bar" : 10, "baz" : 12}

最後は、冒頭のHelloContoroller@Namedを使って、先ほどのバインドしたActorをDIします。
helloActorにSayHelloと送って、その結果をHTTPクライアント向けにマッピングしています。

app/controllers/HelloController.scala

import actors._

@Singleton
class HelloController @Inject()(@Named("configured-actor") helloActor: ActorRef)
(implicit system: ActorSystem, cc:ControllerComponents) extends AbstractController(cc) {

  def index() = Action.async { implicit request: Request[AnyContent] =>
   import actors.HelloActor._
     (helloActor ? SayHello(index)).mapTo[String].map {
        message => Ok(message)
     }
  }

  def login() = Action {
    Ok(views.html.index())    
  }
}

普通にActorをコントローラなどで定義してもよいのですが、DIを使ったほうが、汎用性も高まるし、記述がすっきりするんじゃないかなと考えています。ちなみに、Play FrameworkではGuiceを使うだけがDIをする方法ではありませんマニュアルでDIをするようなやり方などもあり、それぞれに一長一短があるようです。詳細は以下の参考資料などを確認してください。

参考資料

11
9
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
11
9