scalaplayframework-2.3

How to schedule complex tasks using Scala/Play 2.3?


I'm looking to schedule something to run once per day, and the code I want to run involves making updates to entries in the database. I have managed to schedule the running of some simple code by overriding the onStart method in Global.scala using Akka, as follows

override def onStart(app: Application) = {
    Akka.system.scheduler.schedule(0.second, 1.second) {
        println("hello!")
    }
}

The issue is that I want to do something more complicated than logging here, I want to make updates to the database, so I would want to call some function in a models file (models/SomeTable.scala), but I can't import that code in Global.scala.

It sounds like if I want to do something more complicated like that, I should be using Akka's actor system, but I am far from understanding how that works. I've found documentation for how to create Actors through Akka's documentation, though how to incorporate that into my Play project is unclear. Where do I write this Actor class, and how is that imported (what is Global.scala allowed to have access to..?)? And if I don't need to use actors for this, does anyone have some insight into how imports and such work in this part of a Play project?

Note that this part of the Play framework underwent large changes going from Play version 2.3.* to 2.4.*, so it definitely should not be expected that solutions in 2.4.* would likely work here

The information I've gotten above has come mostly from Play's documentation, along with a bunch of related SO questions:

How to schedule task daily + onStart in Play 2.0.4?

how to write cron job in play framework 2.3

Scheduling delaying of jobs tasks in Play framework 2.x app

Where is the job support in Play 2.0?

Was asynchronous jobs removed from the Play framework? What is a better alternative?

Thanks so much in advance!


Solution

  • First of all you definitely need to read about akka.

    But for your specific task you do not need to import anything into Global. You just need to start your worker actor. And this actor can schedule regular action itself. As Template

    import akka.actor.{Actor, Cancellable, Props}
    import scala.concurrent.duration._
    
    class MyActor extends Actor {
      private var cancellable: Option[Cancellable] = None
    
    
      override def preStart(): Unit = {
        super.preStart()
        cancellable = Some(
          context.system.scheduler.schedule(
            1.second,
            24.hours,
            self,
            MyActor.Tick
          )(context.dispatcher)
        )
      }
    
      override def postStop(): Unit = {
        cancellable.foreach(_.cancel())
        cancellable = None
        super.postStop()
      }
    
      def receive: Receive = {
        case MyActor.Tick =>
            // here is the start point for your execution
            // NEW CODE WILL BE HERE
      }
    }
    
    object MyActor {
      val Name = "my-actor"
      def props = Props(new MyActor)
      case object Tick
    }
    

    Here you have an actor class. With preStart and postStop (read more about actors lifecycle) where defined and cancelled schedule. Scheduled action is sending Tick message to self (in other words actor will receive each 24 hours Tick message and if receive is defined for Tick, this message will be processed). So you just need to start you implementation where I placed comment.

    From Global you just need to start this action in onStart:

    Akka.system.actorOf(MyActor.props, MyActor.Name)