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で組み込んでいきます。
@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でコンフィグを取ってくるようにしています。
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が用意されているので、これを使います。
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'に使用するモジュールを追加宣言する必要があります。
play.modules.enabled += "modules.GuiceModule"
foo = {"bar" : 10, "baz" : 12}
最後は、冒頭のHelloContoroller
に@Namedを使って、先ほどのバインドしたActorをDIします。
helloActorにSayHelloと送って、その結果をHTTPクライアント向けにマッピングしています。
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をするようなやり方などもあり、それぞれに一長一短があるようです。詳細は以下の参考資料などを確認してください。