Scheduling a repeating actor at system startup Play 2.4.3 - scala

I've recently started working in Scala and Play Framework and just upgraded a service I've been working on to Play 2.4.3. My end goal is to create a nightly process that starts up a service method in my play application for the purpose of scheduling events via a method call, which I'm currently calling with an Actor.
I had the basic idea of this working through a Global.scala file with an override onStart, but then I saw the play documentation about moving away from the use of GlobalSettings (https://www.playframework.com/documentation/2.4.x/GlobalSettings) and have been trying to move it to an injected dependency approach.
Here's what I've pieced together so far:
Module Code:
import javax.inject._
import com.myOrganization.myPackage.Actors.ScheduleActor
import play.api.libs.concurrent.AkkaGuiceSupport
import play.libs.Akka
import play.api.libs.concurrent.Execution.Implicits.defaultContext
import akka.actor.{ActorRef, ActorSystem}
import scala.concurrent.duration._
import play.Application
import com.google.inject.AbstractModule
#Singleton
class NightlyEvalSchedulerStartup #Inject()(system: ActorSystem, #Named("ScheduleActor") scheduleActor: ActorRef) {
Akka.system.scheduler.schedule(10.seconds, 20.seconds, scheduleActor, "ScheduleActor")
}
class ScheduleModule extends AbstractModule with AkkaGuiceSupport {
def configure() = {
bindActor[ScheduleActor]("ScheduleActor")
bind(classOf[NightlyEvalSchedulerStartup]).asEagerSingleton
}
}
Actor Class:
import akka.actor.{Actor, Props}
import com.myOrganization.myPackage.services.MySchedulingService
object ScheduleActor {
def props = Props[ScheduleActor]
class updateSchedules
}
class ScheduleActor extends Actor {
val MySchedulingService: MySchedulingService = new MySchedulingService
def receive = {
case "runScheduler" => MySchedulingService.nightlyScheduledUpdate()
}
}
Application.conf
play.modules.enabled += "com.myOrganization.myPackage.modules.ScheduleModule"
The service is calling down to a method that is primarily based on scala logic code and database interactions via Anorm.
Every time I try to start the service up with activator start (or run, once an Http request is received) I get the following error:
Oops, cannot start the server.
com.google.inject.CreationException: Unable to create injector, see the following errors:
1) Error injecting constructor, java.lang.RuntimeException: There is no started application
I've tried running the same code by replacing the Aka.system.scheduler... piece with a simple println() and everything seemed to work fine, meaning the service started up and I saw my message on the console. So I'm guessing there's some dependency that I'm missing for the Akka scheduler that is causing it to blowup. Any suggestions you can offer would be great, I've been banging my head against this all day.
EDIT (Solved Code Per Request):
Module Code, with some added code for getting a rough estimation of 3am the next night. This might change down the line, but it works for now:
package com.myOrganization.performanceManagement.modules
import com.myOrganization.performanceManagement.Actors.ScheduleActor
import com.myOrganization.performanceManagement.Actors.ScheduleActor.nightlySchedule
import org.joda.time.{Seconds, LocalDate, LocalTime, LocalDateTime}
import play.api.libs.concurrent.AkkaGuiceSupport
import play.api.libs.concurrent.Execution.Implicits.defaultContext
import akka.actor.{ActorRef, ActorSystem}
import scala.concurrent.duration.{FiniteDuration, SECONDS, HOURS }
import org.joda.time._
import com.google.inject.{Inject, Singleton, AbstractModule}
import com.google.inject.name.Named
class ScheduleModule extends AbstractModule with AkkaGuiceSupport {
override def configure() = {
bindActor[ScheduleActor]("ScheduleActor")
bind(classOf[NightlyEvalSchedulerStartup]).asEagerSingleton()
}
}
#Singleton
class NightlyEvalSchedulerStartup #Inject()(system: ActorSystem, #Named("ScheduleActor") scheduleActor: ActorRef) {
//Calculate initial delay to 3am the next day.
val currentTime: DateTime = DateTime.now
val targetDateTime = currentTime.plusDays(1).withTimeAtStartOfDay()
//Account for Daylight savings to an extent, not mandatory that it starts at 3am, just after midnight.
val initialDelaySeconds = targetDateTime.getHourOfDay match {
case 0 => new Duration(currentTime, targetDateTime.plusHours(3)).getStandardSeconds
case 1 => new Duration(currentTime, targetDateTime.plusHours(2)).getStandardSeconds
}
//Schedule first actor firing to occur at calculated delay and then every 24 hours.
system.scheduler.schedule(FiniteDuration(initialDelaySeconds, SECONDS), FiniteDuration(24, HOURS), scheduleActor, nightlySchedule)
}
Actor:
package com.myOrganization.performanceManagement.Actors
import akka.actor.{ActorSystem, Actor}
import com.google.inject.Inject
import com.myOrganization.performanceManagement.services.PMEvalSchedulingService
object ScheduleActor {
case object nightlySchedule
}
class ScheduleActor #Inject() (actorSystem: ActorSystem) extends Actor {
val pMEvalSchedulingService: PMEvalSchedulingService = new PMEvalSchedulingService
override def receive: Receive = {
case nightlySchedule =>
println("Called the scheduler")
pMEvalSchedulingService.nightlyScheduledEvaluationsUpdate()
}
}

Well in my case the biggest issue ended up being that when scheduling the actor call in NightlyEvalSchedulerStartup() I was calling Akka.system... which was causing the system to instantiate a new AkkaSystem before the app existed. By removing the akk system was came to represent the injected dependency which was ready to go. Hope this helps someone in the future!

Related

DatabaseExecutionContext is missing from the Play API

I am following the DB presription provided here. However, the class DatabaseExecutionContext is nowhere near to be found in the play API and, hence, I am unable to import it.
What am I missing?
See the example code here: https://github.com/playframework/play-samples/blob/2.8.x/play-scala-anorm-example/app/models/DatabaseExecutionContext.scala
import javax.inject._
import akka.actor.ActorSystem
import play.api.libs.concurrent.CustomExecutionContext
#Singleton
class DatabaseExecutionContext #Inject()(system: ActorSystem) extends CustomExecutionContext(system, "database.dispatcher")
You need to configure this context as shown by the documentation: https://www.playframework.com/documentation/2.8.x/AccessingAnSQLDatabase#Using-a-CustomExecutionContext
While you already got an answer, I suggest to use a base trait instead, like:
import javax.inject.{Inject, Singleton}
import akka.actor.ActorSystem
import play.api.libs.concurrent.CustomExecutionContext
import scala.concurrent.ExecutionContext
trait DatabaseExecutionContext extends ExecutionContext
#Singleton
class DatabaseAkkaExecutionContext #Inject()(system: ActorSystem)
extends CustomExecutionContext(system, "database.dispatcher")
with DatabaseExecutionContext
The reason is that if you don't, you'll need to bring akka while testing operations requiring this execution context, with the trait, you should be able to write a simple executor for your tests, like:
implicit val globalEC: ExecutionContext = scala.concurrent.ExecutionContext.global
implicit val databaseEC: DatabaseExecutionContext = new DatabaseExecutionContext {
override def execute(runnable: Runnable): Unit = globalEC.execute(runnable)
override def reportFailure(cause: Throwable): Unit = globalEC.reportFailure(cause)
}
EDIT: I have created a detailed post explaining this.

Start testcontainers before Playframework starts up

I want to start testcontainers from a docker-compose file (postgres and kafka instance), before the play application (with slick) starts up. I want this, so I can write a end to end test. I can not seem to figure out how this is possible with Play.
import java.io.File
import com.dimafeng.testcontainers.DockerComposeContainer.ComposeFile
import com.dimafeng.testcontainers.{DockerComposeContainer, ForAllTestContainer}
import com.typesafe.config.ConfigFactory
import org.scalatest.{BeforeAndAfterAll, FunSpec}
import org.scalatestplus.play.guice.GuiceFakeApplicationFactory
import play.api.inject.guice.GuiceApplicationBuilder
import play.api.{Application, Configuration, Environment, Mode}
trait TestFunSpec extends FunSpec with BeforeAndAfterAll with GuiceFakeApplicationFactory {
override def fakeApplication(): Application = new GuiceApplicationBuilder()
.in(Environment(new File("."), getClass.getClassLoader, Mode.Test))
.loadConfig(_ => Configuration(ConfigFactory.load("test.conf")))
.build
}
class TestIntegrationSpec extends TestFunSpec with ForAllTestContainer {
override val container = DockerComposeContainer(ComposeFile(Left(new File("docker-compose.yml"))))
it("should test something") {
assert(true)
}
}
Scala version 2.12.10
Testcontainer version 0.35.0
Play slick version 5.0.0
When I execute the test without "TestFunSpec", the docker-compose spins up my services correctly. When I add the play application "TestFunSpec" in scope, the application tries to startup, when doing so, it tries to connect with postgres, which is not yet existing (as the testcontainers are started afterwards).
Thnx in advance.
Update: see answer section for an elaborate answer.
After some deep dive in the Play test suite mechanism, I came up with a workable setup.
Step 1, define your test container suite with an AppProvider:
import com.dimafeng.testcontainers.{Container, ForAllTestContainer}
import org.scalatest.Suite
import org.scalatestplus.play.AppProvider
trait PlayTestContainer extends Suite with AppProvider with ForAllTestContainer {
override val container: Container
}
Step 2, create an abstract PlayTestContainerIntegrationSpec which extends the above trait:
import java.io.File
import com.typesafe.config.ConfigFactory
import org.scalatest.concurrent.{IntegrationPatience, ScalaFutures}
import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach, TestData}
import org.scalatestplus.play.PlaySpec
import org.scalatestplus.play.guice.GuiceOneAppPerTest
import play.api.inject.guice.GuiceApplicationBuilder
import play.api.{Application, Configuration, Environment, Mode}
abstract class PlayTestContainerIntegrationSpec
extends PlaySpec
with PlayTestContainer
with GuiceOneAppPerTest
with ScalaFutures
with IntegrationPatience
with BeforeAndAfterEach
with BeforeAndAfterAll {
override def newAppForTest(testData: TestData): Application = application()
def application(): Application =
new GuiceApplicationBuilder()
.in(Environment(new File("."), getClass.getClassLoader, Mode.Test))
.loadConfig(_ => Configuration(ConfigFactory.load("test.conf")))
.build
}
As you can see, we include the "PlayTestContainer" trait and we override the "newAppForTest" function for the construction of a play application.
Step 3, create a specific integration test, extend the above abstract PlayTestContainerIntegrationSpec, and override the container, to your specific need:
class TestIntegrationSpec extends PlayTestContainerIntegrationSpec {
override val container = DockerComposeContainer(ComposeFile(Left(new File("docker-compose.yml"))))
"should test something" in {
assert(true === true)
}
}
Hope this helps.

How to define onStart method in scala play framework 2.4

how can i define startup job in play framework 2.4 with scala?
play framework GlobalSetting
I have already:
class StartupConfigurationModule extends AbstractModule{
override def configure(): Unit = {
Akka.system.scheduler.schedule(Duration(0,duration.HOURS),Duration(24,duration.HOURS))(Id3Service.start())
Akka.system.dispatcher
}
}
You need to register this in the modules.enabled of your application (in application.conf).
It should schedule a call to start on the Id3Service after 0 hours and then every 24 hours.
The issue is that the module doesn't declare a dependency on a running application, or more interestingly on a started actorSystem. Guice can decide to start it before the app is initialized.
The follwing is one way to force the dependency on the initialized actorSystem (and reduce the footprint of your dependency)
import javax.inject.{ Singleton, Inject }
import akka.actor.ActorSystem
import com.google.inject.AbstractModule
import scala.concurrent.duration._
class StartupConfigurationModule extends AbstractModule {
override def configure(): Unit = {
bind(classOf[Schedule]).asEagerSingleton()
}
}
#Singleton
class Schedule #Inject() (actorSystem: ActorSystem) {
implicit val ec = actorSystem.dispatcher
actorSystem.scheduler.schedule(Duration(0, HOURS), Duration(24, HOURS))(Id3Service.start())
}
object Id3Service {
def start(): Unit = println("started")
}

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)

Spray/Akka missing implicit

MyService.scala:33: could not find implicit value for parameter eh: spray.routing.ExceptionHandler
I have run into a "missing implicit" compilation error using Akka, in spray.io code that makes an http call to a separate back-end server, as part of responding to an http get. The code needs to import quite a lot of Spray and Akka libraries, so it's a bit hard figuring whether there may be some library conflicts causing this, and I'd rather figure how to logically trace this sort of problem for this and other cases.
The missing implicit is encountered on calling runRoute(myRoute)
Here's the code:
import spray.routing._
import akka.actor.Actor
import akka.actor.ActorSystem
import spray.http._
import MediaTypes._
import akka.io.IO
import spray.httpx.RequestBuilding._
import scala.concurrent.Future
import spray.can.Http
import spray.http._
import akka.util.Timeout
import HttpMethods._
import akka.pattern.ask
import akka.event.Logging
import scala.concurrent.duration._
// we don't implement our route structure directly in the service actor because
// we want to be able to test it independently, without having to spin up an actor
class MyServiceActor extends Actor with MyService with akka.actor.ActorLogging {
log.info("Starting")
// the HttpService trait defines only one abstract member, which
// connects the services environment to the enclosing actor or test
def actorRefFactory = context
// this actor only runs our route, but you could add
// other things here, like request stream processing
// or timeout handling
def receive = runRoute(myRoute)
}
// this trait defines our service behavior independently from the service actor
trait MyService extends HttpService {
implicit val system: ActorSystem = ActorSystem()
implicit val timeout: Timeout = Timeout(15.seconds)
import system.dispatcher // implicit execution context
//val logger = context.actorSelection("/user/logger")
val logger = actorRefFactory.actorSelection("../logger")
val myRoute =
{
def forward(): String = {
logger ! Log("forwarding to backend")
val response: Future[HttpResponse] =
(IO(Http) ? Get("http:3080//localhost/backend")).mapTo[HttpResponse]
"<html><body><h1>api response after backend processing</h1></body></html>"
}
path("") {
get {
respondWithMediaType(`text/html`) { // XML is marshalled to `text/xml` by default, so we simply override here
complete(forward)
}
}
}
}
}
I am wondering what's the best way to solve this, hopefully providing insight into how to solve similar problems with implicits being missing, as they are somehow inherently not straightforward to track down.
EDIT: when trying to directly pass implicits as in #christian's answer below, I get:
MyService.scala:35: ambiguous implicit values:
both value context in trait Actor of type => akka.actor.ActorContext
and value system in trait MyService of type => akka.actor.ActorSystem
match expected type akka.actor.ActorRefFactory
RoutingSettings.default, LoggingContext.fromActorRefFactory)
^
Not quite sure why being specific as in #christian's answer leaves room for ambiguity for the compiler...
I ran into the same "could not find implicit value for parameter eh: spray.routing.ExceptionHandler" error earlier today. I tried #Christian's approach but saw a few "implicit values for xxx" creeping up. After scouting the error message a little I found adding implicit val system = context.system to the actor that runRoute solved the problem.
runRoute expects a few implicits. You are missing an import:
import spray.routing.RejectionHandler.Default
Update:
I think we also did have some problems with runRoute because we are supplying the implicit parameters explicitly:
runRoute(route)(ExceptionHandler.default, RejectionHandler.Default, context,
RoutingSettings.default, LoggingContext.fromActorRefFactory)
Update2:
To fix the last error, remove the creation of the ActorSystem (in MyService you get the actor system from MyServiceActor - therefore you have to use a self type annotation). This compiles:
import akka.actor.Actor
import akka.io.IO
import spray.httpx.RequestBuilding._
import spray.http.MediaTypes._
import spray.routing.{RoutingSettings, RejectionHandler, ExceptionHandler, HttpService}
import spray.util.LoggingContext
import scala.concurrent.Future
import spray.can.Http
import spray.http._
import akka.util.Timeout
import HttpMethods._
import akka.pattern.ask
import akka.event.Logging
import scala.concurrent.duration._
// we don't implement our route structure directly in the service actor because
// we want to be able to test it independently, without having to spin up an actor
class MyServiceActor extends Actor with MyService with akka.actor.ActorLogging {
log.info("Starting")
// the HttpService trait defines only one abstract member, which
// connects the services environment to the enclosing actor or test
implicit def actorRefFactory = context
// this actor only runs our route, but you could add
// other things here, like request stream processing
// or timeout handling
def receive = runRoute(myRoute)(ExceptionHandler.default, RejectionHandler.Default, context,
RoutingSettings.default, LoggingContext.fromActorRefFactory)
}
// this trait defines our service behavior independently from the service actor
trait MyService extends HttpService { this: MyServiceActor =>
implicit val timeout: Timeout = Timeout(15.seconds)
implicit val system = context.system
//val logger = context.actorSelection("/user/logger")
val logger = actorRefFactory.actorSelection("../logger")
val myRoute =
{
def forward(): String = {
//logger ! Log("forwarding to backend")
val response: Future[HttpResponse] =
(IO(Http) ? Get("http:3080//localhost/backend")).mapTo[HttpResponse]
"<html><body><h1>api response after backend processing</h1></body></html>"
}
path("") {
get {
respondWithMediaType(`text/html`) { // XML is marshalled to `text/xml` by default, so we simply override here
complete(forward)
}
}
}
}
}