2016-07-22 22 views
0

Ich habe eine Play-Scala-Anwendung mit SqLite mit Slick. Meine Tabellen sind wie folgt definiert:Slick Datenbankzugriff in Actor

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

Meine Controller erhalten Zugriff über DI:

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

Wie erhalte ich eine Datenbank-Handle in einem Schauspieler?

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

funktioniert natürlich nicht, da Guice die DataSets-Klasse nicht findet.

Edit: zur Verdeutlichung: Ich möchte den Aktor nicht für den Datenbankzugriff im Controller verwenden (via ask), sondern einige ressourcenintensive Berechnungen vom Controller nach einer Anfrage starten und sie danach in der db ablegen (async).

Antwort

1

Ich habe jetzt einen Weg gefunden, der mit DI integriert, dicht gefolgt von official documentation. Denn für die Notwendigkeit einer ActorContext, InjectedActorSupport kann nur vererbt werden Actor s. Dies bedeutet, dass ich einen Schauspieler erschaffen musste, der nichts unternimmt als neue "Arbeiter" -Aktoren zu instanziieren und zu starten. Vielleicht gibt es einen einfacheren Weg, aber das funktioniert richtig.

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

Und schließlich in der Steuerung:

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

Sie können Abhängigkeiten in einen Schauspieler injizieren:

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

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

Danach nur injizieren Schauspieler in die Steuerung:

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

} 
+0

Was kann ich tun, wenn ich neue Akteure im Controller instanziieren und keine Nachrichten an einen senden möchte? – joni

0

Statt der

@Singleton 
class DataSets 

man es als ein deklarieren einfaches Scala-Objekt, das als DataSetsDAO

fungieren kann
object DataSets 

und dann in den Schauspielern nur DataSets.dbOperation verwenden nur bedenken, dass das Ergebnis Typ, dass eine Zukunft sein wird, so dass nur eine Nachricht auf den onComplete in dem Schauspieler selbst plane Nebenwirkungen zu vermeiden.

+0

hm Ich denke, das wäre eine einfache Lösung, aber dann wird das Testen unmöglich gemacht – joni

+0

Sie können Integrationstests in der Testdatenbank ausführen, andernfalls übergeben Sie einfach die injizierte DataSets-Instanz vom Controller über den Actor Props http: //doc.akka .io/docs/akka/2.4.8/scala/akteurs.html # Requisiten –