Skip to content

Delayed job framework for Playframework on Heroku

License

Notifications You must be signed in to change notification settings

GIVESocialMovement/moonlight

Repository files navigation

Moonlight

CircleCI codecov

Moonlight is a simple delayed job (or, in other words, background job) framework for Playframework.

Here are important notes:

  • Moonlight uses Postgresql through Slick. This requires you to configure Slick correctly.
  • Use JSON to serialize/deserialize job's params.

At GIVE.asia, we've built Moonlight because we wanted a delayed job framework for moderate load. At the same time, we want to avoid introducing a new datastore to our system.

We are using Playframework on Heroku with Postgresql (through Slick), so Moonlight fits perfectly into our setting.

Usage

The usage requires some degree of involvement.

Please see a full working example in the folder test-project and the live demo here: https://moonlight-test.herokuapp.com. (The worker dyno isn't free, so it's off. Therefore, the job will be added but not processed).

1. Setup Postgresql's table

We've provided a set of SQLs for creating/updating required schemas. Please see conf/evolutions/default/1.sql.

Please apply the SQLs to your database.

2. Include Moonlight in your build.sbt

resolvers += Resolver.bintrayRepo("givers", "maven")
libraryDependencies += "givers.moonlight" %% "play-moonlight" % "x.x.x"

The artifacts are hosted here: https://bintray.com/givers/maven/play-moonlight

3. Create an executor

You can define a delayed job by providing WorkerSpec which allows you to specify params and job runner. Here's an example:

@Singleton
class SimpleExecutor @Inject()(implicit ec: ExecutionContext)
    extends JobExecutor(SimpleExecutor.Type) {

  override def run(data: SimpleExecutor.Job): Future[Unit] = Future.successful(())
}

object SimpleExecutor {
  case class Job(data: String)

  implicit case object Type extends JobTypeJson[Job]("Simple")(Json.format)
}

4. Create a scheduled job

class SimpleScheduledJob @Inject() (implicit val executionContext: ExecutionContext) extends ScheduledJob {
  type IN = String
    
  private val logger = Logger(this.getClass)
  override def run(input: IN): Future[Unit] = {
    logger.info(s"[$input]")
    Future.unit
  }
}

5. Install Moonlight's module

Create a module with defined SimpleExecutor and SimpleScheduledJob:

class MoonlightModule extends play.api.inject.Module {
  def bindings(environment: Environment, configuration: Configuration)  = Seq(
    bind[MoonlightSettings].toInstance(new MoonlightSettings(
        parallelism = Runtime.getRuntime.availableProcessors(),
        pauseDurationWhenNoJobs = 10.seconds,
        maintenanceInterval = 1.minutes,
        betweenRunAttemptInterval = 10.minutes,
        countMetricsCollectionInterval = 1.minute,
        maxJobRetries = 3,
        jobRunTimeout = 10.seconds,
        completedJobsTtl = (24 * 30).hours,
        executors = Seq(simpleExecutor),
        schedulerInputs = Seq(
          SchedulerInput[SimpleScheduledJob]("job1", cronExpression("0 * * * * ? *"), 10.seconds)
        )
    ))
  )
}

Then, install the module and configure Slick's database connection in application.conf by:

play.modules.enabled += "modules.MoonlightModule"

slick.dbs.default.db.properties.url="postgres://user:pass@localhost:5432/database"

6. Run Moonlight

You can run Moonlight locally with sbt 'runMain givers.moonlight.v2.MoonlightApplication dev'.

On Heroku, you can run Moonlight by adding the below line to Procfile:

moonlight: target/universal/stage/bin/giveasia -Dconfig.resourceapplication.conf -main givers.moonlight.v2.MoonlightApplication -- prod

Please see a full example in the folder test-project-v2.

Interested in using the framework?

Please don't hesitate to ask questions by opening a Github issue. We want you to use Moonlight successfully.