【问题标题】:Slick database access in ActorActor 中的灵活数据库访问
【发布时间】:2016-07-22 15:27:41
【问题描述】:

我有一个使用 SqLite 和 slick 的 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进行数据库访问(通过询问),而是在请求后从控制器开始一些资源密集型计算,然后将它们存储在数据库中(异步) .

【问题讨论】:

    标签: scala playframework akka guice slick


    【解决方案1】:

    我现在找到了一种与 DI 集成的方法,紧跟 official documentation。因为ActorContext的需要,InjectedActorSupport只能被Actors继承。这意味着我必须创建一个除了实例化和启动新的“工人”Actor之外什么都不做的actor。也许有一种更简单的方法,但这可以正常工作。

    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)
      }
    

    【讨论】:

      【解决方案2】:

      您可以将依赖项注入到参与者中:

      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))
        }
      
      }
      

      【讨论】:

      • 如果我希望能够在控制器中实例化新的 Actor 而不是向其中一个发送消息,该怎么办?
      【解决方案3】:

      而不是拥有

      @Singleton
      class DataSets 
      

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

      object DataSets
      

      然后在actor中使用DataSets.dbOperation,记住结果类型将是一个Future,所以只需在onComplete上的actor中安排一条消息给self以避免任何副作用。

      【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-08-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-19
      • 1970-01-01
      • 2016-10-20
      相关资源
      最近更新 更多