在Actor中进行光滑的数据库访问

时间:2016-07-22 15:27:41

标签: scala playframework akka guice slick

我有一个使用SqLite和sclick的play-scala应用程序。我的表定义如下:

@Singleton
class DataSets @Inject()(protected val dbConfigProvider: DatabaseConfigProvider, keys: PublicKeys) extends DataSetsComponent
  with HasDatabaseConfigProvider[JdbcProfile] {
  import driver.api._

  val DataSets = TableQuery[DataSetsTable]

  def all = db.run(DataSets.sortBy { _.id }.result)
  ...
}

我的控制器可以通过DI访问:

@Singleton
class DataSetsController @Inject() (dataSets: DataSets, env: play.Environment) extends Controller {
...

如何在Actor中获取数据库句柄?

class TrainActor @Inject() (dataSets: DataSets) extends Actor {
...

当然不起作用,因为Guice没有找到DataSets类。

编辑:澄清:我不想在控制器中使用actor进行数据库访问(通过ask),但是在请求之后从控制器启动一些资源密集型计算并在之后将它们存储在db中(异步)

3 个答案:

答案 0 :(得分:1)

我现在找到了一种与DI集成的方法,紧跟http://www.w3schools.com/tags/tag_object.asp之后。因为ActorContext的需要,InjectedActorSupport只能由Actor继承。这意味着我必须创建一个演员,除了实例化并启动新的" worker"演员。也许有一种更简单的方法,但这可以正常工作。

TrainActor.scala

package actors

import javax.inject.Inject

import akka.actor._
import com.google.inject.assistedinject.Assisted
import models.{DataSet, Model, PublicKey}
import play.api.Logger
import tables.DataSets

import scala.concurrent.ExecutionContext.Implicits.global

object TrainActor {
  case object Start
  case class LoadData(d: DataSet, k: PublicKey)

  trait Factory {
    def apply(model: Model): Actor
  }
}

class TrainActor @Inject() (val dataSets: DataSets, @Assisted val model: Model) extends Actor {
  import TrainActor._

  def receive = {
    case Start =>
      dataSets.findWithKey(model.id.get)
      ...

TrainActorStarter.scala

package actors

import javax.inject.Inject

import akka.actor.{Actor, ActorRef}
import models.Model
import play.api.libs.concurrent.InjectedActorSupport

object TrainActorStarter {
  case class StartTraining(model: Model)
}

/**
  * https://www.playframework.com/documentation/2.5.x/ScalaAkka#Dependency-injecting-actors
  * @param childFactory
  */
class TrainActorStarter @Inject() (childFactory: TrainActor.Factory) extends Actor with InjectedActorSupport {
  import TrainActorStarter._

  def receive = {
    case StartTraining(model: Model) =>
      val trainer: ActorRef = injectedChild(childFactory(model), s"train-model-model-${model.id.get}")
      trainer ! TrainActor.Start
  }
}

ActorModule.scala

package actors

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

class ActorModule extends AbstractModule with AkkaGuiceSupport {
  def configure(): Unit = {
    bindActor[TrainActorStarter]("train-actor-starter")
    bindActorFactory[TrainActor, TrainActor.Factory]
  }
}

最后在控制器中:

package controllers

import javax.inject._

import actors.{TrainActorStarter, TrainCallbackActor}
import akka.actor.{ActorRef, ActorSystem, _}
import akka.stream.Materializer
...

@Singleton
class ModelsController @Inject() (implicit system: ActorSystem, materializer: Materializer, ..., @Named("train-actor-starter") trainActorStarter: ActorRef) extends Controller with InjectedActorSupport {

  def startTraining(model: Model): Unit = {
    if(model.id.isEmpty) return
    trainActorStarter ! TrainActorStarter.StartTraining(model)
  }

答案 1 :(得分:0)

您可以将依赖项注入actor:

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

class MyModule extends AbstractModule with AkkaGuiceSupport {
  def configure = {
    bindActor[TrainActor]("injected-train-actor")
  }
}

之后只需将actor注入控制器:

class MyController @Inject()(@Named("injected-train-actor") trainActor: ActorRef) {

  def endpointTest = Action.async {
    for {
      items <- (trainActor ? FetchAll).mapTo[Seq[DataSetsTableRow]]
    } yield Ok(Json.toJson(items))
  }

}

答案 2 :(得分:0)

而不是

@Singleton
class DataSets 

可以将它声明为可以充当DataSetsDAO的简单scala对象

object DataSets

然后在演员只需使用DataSets.dbOperation时请记住,结果类型将是Future,所以只需在onComplete上的演员中安排自己的消息以避免任何副作用。