Slick database access in Actor - scala

I have a play-scala application using SqLite with slick. My tables are defined like this:
#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)
...
}
My controllers get access via DI:
#Singleton
class DataSetsController #Inject() (dataSets: DataSets, env: play.Environment) extends Controller {
...
How do I get a database handle in an Actor?
class TrainActor #Inject() (dataSets: DataSets) extends Actor {
...
of course does not work as Guice does not find the DataSets class.
Edit: to clarify: I do not want to use the actor for database access in the controller (via ask), but to start some resource intensive computations from the controller after a request and store them in the db afterwards (async).

I now found a way which integrates with DI, closely following the official documentation. Because for the need of an ActorContext, InjectedActorSupport can only be inherited by Actors. This means I had to create an actor which does nothing than instantiate and start new "worker" Actors. Maybe there is an easier way, but this works correctly.
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]
}
}
And finally in the controller:
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)
}

You can inject dependencies into an 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")
}
}
After that just inject actor into controller:
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))
}
}

Instead of having
#Singleton
class DataSets
one can declare it as a simple scala object that can act as the DataSetsDAO
object DataSets
and then in the actor just use DataSets.dbOperation just bear in mind that the result type of that will be a Future, so just schedule a message to self in the actor on the onComplete to avoid any side effects.

Related

Composing and outputting two future asynchronous actions on screen

I hate to ask - I really do but this one has got me for the moment..
I'm trying to compose some actions (in Play Framework & scala) with my main guide being this vid. However it was made a few years back so some of the functionality has since been deprecated and therefore I have had to find work-arounds as I go. Currently I am trying to output two asynchronous actions within some HTML markup.
I successfully outputted one action with this controller:
package controllers
import akka.actor.ActorSystem
import javax.inject._
import play.api.mvc._
import services.ServiceClient
import scala.concurrent.ExecutionContext
#Singleton
class AsyncController #Inject() (sc: ServiceClient)(actorSystem: ActorSystem)(implicit exec: ExecutionContext) extends Controller {
def index = Action.async { request =>
val asy1 = sc.makeServiceCall("async1")
for {
async1Message <- asy1
} yield {
Ok(views.html.async1.async1(async1Message))
}
}
}
In case you are wondering the sc.makeServiceCall refers to this file:
class ServiceClient #Inject() (ws: WSClient) {
def makeServiceCall(serviceName: String): Future[String] = {
ws.url(s"http://localhost:9000/mock/$serviceName").get().map(_.body)
}
}
So I followed the video in its' guidance to compose two asynchronous actions with some HTML. And this is where it gets difficult/interesting/upsetting:
package controllers
import javax.inject.Inject
import akka.actor.ActorSystem
import play.api.mvc._
import scala.concurrent.{ExecutionContext}
import Ui.Pagelet
class AsyncHomeController #Inject() (as1: AsyncController)(as2: Async2Controller)(actorSystem: ActorSystem)(implicit exec: ExecutionContext) extends Controller {
def index = Action.async { request =>
val asy1 = as1.index(request)
val asy2 = as2.index(request)
for {
async1Result <- asy1
async2Result <- asy2
async1Body <- Pagelet.readBody(async1Result)
async2Body <- Pagelet.readBody(async2Result)
} yield {
Ok(views.html.home2(async1Body, async2Body))
}
}
}
So Async2Controller is very similar to AsyncController and Pagelet.readBody refers to this:
package Ui
import play.api.libs.iteratee.Iteratee
import play.api.mvc.{Codec, Result}
import play.twirl.api.Html
import scala.concurrent._
object Pagelet {
def readBody(result: Result)(implicit codec: Codec): Future[Html] = {
result.body.run(Iteratee.consume()).map(bytes => Html(new String(bytes, codec.charset)))
}
}
And this is wherein the error lies - which is:
value run is not a member of play.api.http.HttpEntity
I cannot find documentation on whether it needs to be injected or any indication that it has since been deprecated. If someone has got an answer to this or a work-around please divulge. Many thanks
The Iteratee lib is deprecated and was replaced by akka-stream. You need to change the implementation of readBody:
def readBody(result: Result)(implicit mat: Materializer, ec: ExecutionContext, codec: Codec): Future[Html] = {
result.body.consumeData.map(byteString => Html(codec.decode(byteString))
}
You also need to change the dependencies of the controller to get a Materializer:
class AsyncHomeController #Inject() (as1: AsyncController, as2: Async2Controller)(actorSystem: ActorSystem)(implicit exec: ExecutionContext, mat: Materializer)
Edit: code updated

How can a custom ApplicationLoader start Dependency Injecting Actors and get tested?

In "Dependency Injecting Actors" it's shown how to inject a parameter into the constructor of a child actor. The parent actor uses injectedChild to be allowed to pass to the child (at child creation time) only the non-injected parameter and then let Guice inject the rest. To do this, it extends InjectedActorSupport and gets the child's factory injected in the constructor:
class MyParent #Inject() (childFactory: MyChild.Factory,
#Assisted something: Something,
#Assisted somethingElse: SomethingElse) extends Actor with InjectedActorSupport
[..]
val child: ActorRef = injectedChild(childFactory(something, somethingElse), childName)
But what about the class that starts the parent and is not an actor but a custom ApplicationLoader?
How can I start the parent actor from there? No mention of this is in the documentation.
I tried doing the same for the loader as I did for parent:
class MyLoader #Inject() (parentFactory: MyParent.Factory) extends ApplicationLoader with Actor with InjectedActorSupport {
[..]
val parent = injectedChild(parentFactory(something, somethingElse), parentName)
would this be correct? How can I test it?
class MyModule extends AbstractModule with AkkaGuiceSupport {
def configure = {
bindActor[MyParent](parentName)
bindActor[MyLoader](loaderName)
bindActorFactory[MyChild, MyChild.Factory]
bindActorFactory[MyParent, MyParent.Factory]
}
}
So:
How do I start the parent from MyLoader while letting Guice dependency-inject what's required?
How can I test MyLoader?
This has been my test so far but now I need to pass the injected thingy to MyLoader and I don't know how (note the ***???**** in place of the argument which I do not know where to find):
class MyLoaderSpec(_system: ActorSystem, implicit val ec: ExecutionContext) extends TestKit(_system) with WordSpecLike with BeforeAndAfterAll with Matchers {
val loader = new SimstimLoader(???)
override def beforeAll(): Unit = {
loader.load(ApplicationLoader.createContext(new Environment(new File("."), ApplicationLoader.getClass.getClassLoader, Mode.Test)))
}
Thanks a million in advance!
Here is how I solved this issue.
--> How to start a parent actor who needs dependency-injection.
First of all, manually starting such an actor is impossible if you, like me, need to dependency-inject an instance which you do not know how to pass and where from. The solution is to let Guice start the actor automagically. Here is how.
First, create your binder module for Guice:
class MyModule extends AbstractModule with AkkaGuiceSupport{
override def configure(): Unit = {
bindActor[Root](Root.NAME)
bind(classOf[StartupActors]).asEagerSingleton()
}
}
Then, tell Play where your binder module is located by adding the following in your conf/application.conf:
play.modules={
enabled += "my.path.to.MyModule"
}
The StartupActors is simply a class I use to log whenever the automagic start of dependency-injected actors actually takes place. I log the event so that I can be sure of when and whether it occurs:
class StartupActors #Inject() (#Named(Root.NAME) root: ActorRef) {
play.api.Logger.info(s"Initialised $root")
}
The Root actor in my case takes care of parsing a custom configuration. Since the resulting vars from the parsing is required by my parent actor and during the tests I need to mock such resulting vars, I delegate the parsing to an actor other than the parent actor, i.e., the Root actor:
object Root {
final val NAME = "THERoot"
case class ParseConfiguration()
}
class Root #Inject()(configuration: Configuration, projectDAO: ProjectDAO) extends Actor {
val resultingVar: Something = myConfigParsing()
override def preStart(): Unit = {
context.actorOf(Props(new MyParent(resultingVar: Something, somethingElse: SomethingElse, projectDAO: ProjectDAO)))
}
override def receive: Receive = {
case ParseConfiguration => sender ! myConfigParsing()
case _ => logger.error("Root actor received an unsupported message")
}
}
The ParseConfiguration message is used uniquely for testing purposes. Normally the configuration parsing occurs instead because of the initialisation of the resultingVar attribute.
This way, MyParent wont need to get anything injected. Only StartupActors and Root will get injected. MyParent will simply get projectDAO from Root and pass it on to all its children.
class MyParent(something: Something, somethingElse: SomethingElse, projectDAO: ProjectDAO) extends Actor { ... }
Finally, for completion, I'm reporting here how I wrote the tests since I had troubles finding enough information online around this as well.
import akka.actor.{ActorRef, ActorSystem, Props}
import akka.testkit.{TestKit, TestProbe}
import com.typesafe.config.ConfigFactory
import org.mockito.Mockito.mock
import org.scalatest.{BeforeAndAfterAll, WordSpecLike}
import org.specs2.matcher.MustMatchers
import play.api.Configuration
import scala.concurrent.ExecutionContext
class RootSpec(_system: ActorSystem) extends TestKit(_system)
with WordSpecLike with BeforeAndAfterAll with MustMatchers {
implicit val ec: ExecutionContext = scala.concurrent.ExecutionContext.global
val conf: com.typesafe.config.Config = ConfigFactory.load()
val configuration: Configuration = Configuration(conf)
val projectDAOMock: ProjectDAO = mock(classOf[ProjectDAO])
private var mainActor: ActorRef = _
private var something: Something = Something.empty
def this() = this(ActorSystem("MySpec"))
override def afterAll: Unit = {
system.shutdown()
}
override def beforeAll(): Unit = {
mainActor = system.actorOf(Props(new Root(configuration, projectDAOMock)), Root.NAME)
}
"RootSpec: Root Actor" should {
val probe = TestProbe()
"successfully parse the configuration file" in {
probe.send(mainActor, ParseConfiguration)
something = probe.expectMsgPF() {
case msg => msg.asInstanceOf[Something]
}
}
}
}
and then I test MyParent by conveniently providing mock objects in place of vars resulting from the configuration parsing:
import akka.actor.{ActorRef, ActorSystem, Props}
import akka.testkit.{TestKit, TestProbe}
import org.mockito.Mockito
import org.mockito.Mockito._
import org.scalatest.{BeforeAndAfterAll, WordSpecLike}
import org.specs2.matcher.MustMatchers
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.{ExecutionContext, Future}
case class AnyProjectAPI(val projectAPI: ProjectAPI) extends AnyVal
class MyParentSpec(_system: ActorSystem, implicit val ec: ExecutionContext) extends TestKit(_system)
with WordSpecLike with BeforeAndAfterAll with MustMatchers {
val something = mock(classOf[Something])
val somethingElse = mock(classOf[somethingElse])
val projectDAOMock: ProjectDAO = mock(classOf[ProjectDAO])
val projectTest: ProjectAPI = new ProjectAPI(allMyRandomConstructorArguments),
val projectsList: List[ProjectAPI] = List(projectTest)
val expectedCreationId = 1
private var parent: ActorRef = _
def this() = this(ActorSystem("MySpec"), scala.concurrent.ExecutionContext.global)
override def afterAll: Unit = {
system.shutdown()
}
override def beforeAll(): Unit = {
parent = system.actorOf(Props(new MyParent(something, somethingElse, projectDAOMock)), MyParent.NAME)
}
"MyParentTesting: parent's pull request" should {
when(myProjApi.getAllProjects).thenReturn(Future {projectsList})
val anyProject: AnyProjectAPI = AnyProjectAPI(org.mockito.Matchers.any[ProjectAPI])
Mockito.when(projectDAOMock.create(org.mockito.Matchers.any[ProjectAPI]))
.thenReturn(Future {expectedCreationId}: Future[Int])
val probe = TestProbe()
val probe1 = TestProbe()
"be successfully satisfied by all children when multiple senders are waiting for an answer" in {
probe.send(parent, UpdateProjects)
probe1.send(parent, UpdateProjects)
allChildren.foreach(child =>
probe.expectMsg(expectedCreationId))
allChildren.foreach(child =>
probe1.expectMsg(expectedCreationId))
}
}
}

creating a scheduled task in scala with play 2.5

I want to create a scheduled task in Play 2.5. I found some resources related to this topic but none of them were for Play 2.5. I found out this resource related to what I am looking for and it looks good. Also on the same link there is a migration guide from 2.4 to 2.5.
The examples from older versions used GlobalSettings as base but this was deprecated in 2.5. The migration guide is important because it says that we should use dependency injection instead of extending this trait. I am not sure how to do that.
Can you give me some guidance?
You need to run sheduled task inside Akka Actor:
SchedulerActor.scala
package scheduler
import javax.inject.{Inject, Singleton}
import akka.actor.Actor
import org.joda.time.DateTime
import play.api.Logger
import scala.concurrent.ExecutionContext
#Singleton
class SchedulerActor #Inject()()(implicit ec: ExecutionContext) extends Actor {
override def receive: Receive = {
case _ =>
// your job here
}
}
Scheduler.scala
package scheduler
import javax.inject.{Inject, Named}
import akka.actor.{ActorRef, ActorSystem}
import play.api.{Configuration, Logger}
import scala.concurrent.ExecutionContext
import scala.concurrent.duration._
class Scheduler #Inject() (val system: ActorSystem, #Named("scheduler-actor") val schedulerActor: ActorRef, configuration: Configuration)(implicit ec: ExecutionContext) {
val frequency = configuration.getInt("frequency").get
var actor = system.scheduler.schedule(
0.microseconds, frequency.seconds, schedulerActor, "update")
}
JobModule.scala
package modules
import com.google.inject.AbstractModule
import play.api.libs.concurrent.AkkaGuiceSupport
import scheduler.{Scheduler, SchedulerActor}
class JobModule extends AbstractModule with AkkaGuiceSupport {
def configure() = {
bindActor[SchedulerActor]("scheduler-actor")
bind(classOf[Scheduler]).asEagerSingleton()
}
}
application.conf
play.modules.enabled += "modules.JobModule"
If you don't want to use akka, you can use java:
ScheduledFuture
ScheduledExecutorService
DemoDaemon.scala:
import java.util.concurrent.{Executors, ScheduledFuture, TimeUnit}
import javax.inject._
import play.Configuration
import scala.util.Try
class DemoDaemon #Inject() (conf: Configuration) {
val isEnabled = conf.getBoolean("daemon.enabled")
val delay = conf.getLong("daemon.delay")
private var scheduledTaskOption : Option[ScheduledFuture[_]] = None
def task(): Unit = {
Try {
println("doSomething")
} recover {
case e: Throwable => println(e.getMessage)
}
}
def start(): Unit = {
if (isEnabled) {
val executor = Executors.newScheduledThreadPool(1)
scheduledTaskOption = Some(
executor.scheduleAtFixedRate(
new Runnable {
override def run() = task()
},
delay, delay, TimeUnit.SECONDS
)
)
} else {
println("not enabled")
}
}
def stop(): Unit = {
scheduledTaskOption match {
case Some(scheduledTask) =>
println("Canceling task")
val mayInterruptIfRunning = false
scheduledTask.cancel(mayInterruptIfRunning)
case None => println("Stopped but was never started")
}
}
}
DaemonService.scala
import javax.inject.Inject
import play.api.inject.ApplicationLifecycle
import scala.concurrent.Future
class DaemonService #Inject() (appLifecycle: ApplicationLifecycle, daemon: DemoDaemon) {
daemon.start()
appLifecycle.addStopHook{ () =>
Future.successful(daemon.stop())
}
}
JobModule.scala
import com.google.inject.AbstractModule
class JobModule extends AbstractModule {
def configure(): Unit = {
bind(classOf[DaemonService]).asEagerSingleton()
}
}
application.conf
daemon.enabled = true
daemon.delay = 10
play.modules.enabled += "com.demo.daemon.JobModule"

Play 2.4 create an actor for handle websocket with Guice

first of all thank you all for taking some time to review my problem. I'm a newbie in scala ecosystem so I think I'm confussing some concepts.
I'm introducing Guice in a Play 2.4 project and it's working in some REST controllers. I've edited build.sbt to append routesGenerator := InjectedRoutesGenerator as Play official doc recomends and edited my routes as you can see in the following example:
GET /socket #com.letgo.chat.controllers.ChatController.socket
My module for injecting actors looks like:
class ChatModule extends AbstractModule with ScalaModule with AkkaGuiceSupport with GuiceAkkaActorRefProvider {
override def configure() {
bindActor[TalkerProviderActor](TalkerProviderActor.name)
...
And all of this seems to be working.
But another endpoint handles a websocket with Play method WebSocket.acceptWithActor. I need to create an actor which also needs some dependencies.
The controller creates a ConnectionActor:
class ConnectionActor(
#Assisted() websocket: ActorRef,
monitoring: Monitoring,
#Named(BouncerActor.name) bouncer: ActorRef,
) extends Actor
...
class ChatController #Inject() extends Controller {
def socket(): WebSocket[String, JsValue] = WebSocket.acceptWithActor[String, JsValue] { request => out =>
// I had the following statement in order to build the actor before introducing Guice:
// ConnectionActor.props()
// Now, I need some magic here
}
}
So as you can see I need a websocket: ActorRef for the websocket output.
I've created some actors in other parts of the application using bindActorFactory method provided by AkkaGuiceSupport:
bindActorFactory[TalkerActor, TalkerActor.Factory]
But I don't know how I should create an actor for handling the websocket in this case. Can you guys help me?
Thank you
I believe this can do the trick:
package controllers
import javax.inject._
import akka.actor.{Actor, ActorRef, ActorSystem, Props}
import akka.stream.Materializer
import play.api.libs.streams.ActorFlow
import play.api.libs.ws.WSClient
import play.api.mvc._
import scala.concurrent.ExecutionContext.Implicits.global
#Singleton
class SocketActorProvider #Inject() (ws: WSClient) {
def get(out: ActorRef) = Props(new SocketActor(out, ws))
}
class SocketActor(out: ActorRef, ws: WSClient) extends Actor {
override def receive: Receive = {
case "ping" => ws.url("http://localhost:9000/pong").get().foreach(pong => out ! pong.body)
}
}
#Singleton
class HomeController #Inject() (implicit system: ActorSystem, ws: WSClient, materializer: Materializer, provider: SocketActorProvider) extends Controller {
// route '/ws'
def socket = WebSocket.accept[String, String] { request =>
ActorFlow.actorRef(out => provider.get(out))
}
// route '/pong'
def pong = Action {
Ok("PONG!")
}
// route '/'
def index = Action {
Ok("""
<script>
var ws = new WebSocket('ws://localhost:9000/ws');
ws.onopen = () => console.log('I am open!');
ws.onmessage = m => console.log(m.data);
setInterval(() => {console.log('ping'); ws.send('ping')}, 1000)
</script>
""").as("text/html")
}
}
As you can see there is a provider injected by Guice which had WSClient injected and when building an actor it passes in the dependency

Implementing Akka in Play Framework 2.4 for Scala

I am trying to replicate the basic example proposed in the Integrating with Akka, Play 2.4 for Scala doc. But I have difficulties in placing the final pieces together...
I have defined the actor (see paragraph Writing actors) at app/actors/HelloActor.scala with the following code:
package actors
import akka.actor._
object HelloActor {
def props = Props[HelloActor]
case class SayHello(name: String)
}
class HelloActor extends Actor {
import HelloActor._
def receive = {
case SayHello(name: String) =>
sender() ! "Hello, " + name
}
}
Then (see Creating and using actors) I suppose I should create a controller at app/controllers/Hello.scala with something like:
package controllers
import play.api.mvc._
import akka.actor._
import javax.inject._
import actors.HelloActor
#Singleton
class Hello #Inject() (system: ActorSystem) extends Controller {
val helloActor = system.actorOf(HelloActor.props, "hello-actor")
...
}
The question: where and how I utilize the code in the following paragraph Asking things of actors to have a working solution? I have tried to add it to the above Hello.scala controller but without success.
import play.api.libs.concurrent.Execution.Implicits.defaultContext
import scala.concurrent.duration._
import akka.pattern.ask
implicit val timeout = 5.seconds
def sayHello(name: String) = Action.async {
(helloActor ? SayHello(name)).mapTo[String].map { message =>
Ok(message)
}
}
Found the solution, I had some problems with defining the implicit timeout, this is the working controller:
package controllers
import play.api.mvc._
import akka.actor._
import javax.inject._
import actors.HelloActor
import actors.HelloActor.SayHello
import play.api.libs.concurrent.Execution.Implicits.defaultContext
import scala.concurrent.duration._
import akka.pattern.ask
import akka.util.Timeout
#Singleton
class Hello #Inject() (system: ActorSystem) extends Controller {
val helloActor = system.actorOf(HelloActor.props, "hello-actor")
implicit val timeout: Timeout = 5.seconds
def sayHello(name: String) = Action.async {
(helloActor ? SayHello(name)).mapTo[String].map { message ⇒
Ok(message)
}
}
}
Plus I added the following route in app/conf/routes:
# Actor test
GET /hello/:name controllers.Hello.sayHello(name)