19

I have a config file servers.conf in my conf/ directory that is read by my ServerController whenever the route /servers is hit. This isn't performant because it requires a re-read of the configuration file on each successive hit when the file won't change. Further if there are problems with the config file, I can tell the user ASAP rather than throw an exception on a page hit.

Currently I have this in my ServerController.scala:

case class Server(ip: String, port: String)

/**
  * This controller creates an `Action` to handle HTTP requests to the
  * application's server page.
  */
@Singleton
class ServerController @Inject() extends Controller {

  /**
    * Create an Action to render an HTML page with a the list of servers.
    * The configuration in the `routes` file means that this method
    * will be called when the application receives a `GET` request with
    * a path of `/servers`.
    */
  def index = Action {

    val serverList = ConfigFactory.load().getConfigList("servers")
    val servers: List[Server] = serverList match {
      case null => Nil
      case _ => serverList map { s =>
        Server(s.getString("ip"), s.getString("port"))
      } filter { s =>
        s.ip != null && s.port != null
      }.toList
    }

    Ok(views.html.servers(servers))
  }
}

My goal is to have the server read the config file at startup and pass the list of servers to the ServerController when the route is hit if there are no problems reading in the config file. If there are problems, I want an exception to be thrown immediately.

I can't seem to find an entry point for my application, though, so I don't know how to perform actions on startup.

Does anyone know how to do this? I'm using Play 2.5.x.

erip
  • 16,374
  • 11
  • 66
  • 121

1 Answers1

21

If you're using the latest version of Play, it looks on startup for any class called Module that is in the root package (that is, there is no package definition at the top of the file). Here is an example taken from the latest Activator template for Play 2.5.x, which I have modified for demonstration of running code on application startup and shutdown:

In services/Say.scala, this would be a simple service to say "Hello!" on startup and "Goodbye!" when the application shuts down:

package services

import javax.inject._
import play.api.inject.ApplicationLifecycle
import scala.concurrent.Future

trait Say {
  def hello(): Unit
  def goodbye(): Unit
}

@Singleton
class SayImpl @Inject() (appLifecycle: ApplicationLifecycle) extends Say {  
    override def hello(): Unit = println("Hello!")
    override def goodbye(): Unit = println("Goodbye!")

    // You can do this, or just explicitly call `hello()` at the end
    def start(): Unit = hello()

    // When the application starts, register a stop hook with the
    // ApplicationLifecycle object. The code inside the stop hook will
    // be run when the application stops.
    appLifecycle.addStopHook { () =>
        goodbye()
        Future.successful(())
    }

    // Called when this singleton is constructed (could be replaced by `hello()`)
    start()
}

In Module.scala,

import com.google.inject.AbstractModule
import services._

/**
 * This class is a Guice module that tells Guice how to bind several
 * different types. This Guice module is created when the Play
 * application starts.

 * Play will automatically use any class called `Module` that is in
 * the root package. You can create modules in other locations by
 * adding `play.modules.enabled` settings to the `application.conf`
 * configuration file.
 */
class Module extends AbstractModule {

  override def configure() = {
    // We bind the implementation to the interface (trait) as an eager singleton,
    // which means it is bound immediately when the application starts.
    bind(classOf[Say]).to(classOf[SayImpl]).asEagerSingleton()
  }
}

Some further resources you may find useful are the Scala dependency injection (DI) documentation and the Guice documentation. Guice is the default DI framework used by Play.

Eric
  • 1,114
  • 9
  • 13
  • Nice. This seems to be pretty close to what I want. Do you know in which directory `root` should live? – erip Apr 06 '16 at 15:27
  • 1
    Well, it's not a directory named `root`. It's just a file that lives in the root package, which is to say it has no `package` declaration at the top. Alternatively, you can define the module somewhere else, say in a `modules` directory, and enable it in your `application.conf` like so: `play.modules.enabled += "com.example.modules"`, where `com.example.modules` should be the package to which your module belongs. – Eric Apr 06 '16 at 15:31
  • Right, so my `root` package is expected to live in `app/` by default? That is, if I put `Say.scala` in `service/` and `Module.scala` lives in `app/` packaged as `root`, it should work? – erip Apr 06 '16 at 15:33
  • 1
    I updated my previous comment since I misunderstood what was being asked. It's in `app/` by default in the template, but it doesn't have to be. Just don't add a `package` declaration to `Module.scala` and it _should_ work. – Eric Apr 06 '16 at 15:34
  • 2
    This seems to only work once the user hits `/` for the first time. I can live with this, but is there no way to do this all before? – erip Apr 06 '16 at 15:49
  • 1
    By the way, this is a fantastic answer. – erip Apr 06 '16 at 15:51
  • 1
    @erip I'm fairly new to DI, but compile-time DI might be what you're looking for: https://www.playframework.com/documentation/2.5.x/ScalaCompileTimeDependencyInjection – Eric Apr 06 '16 at 17:43
  • Does this work without first making an HTTP request to startup play? I have code right now in my Controller and has the same behavior: A GET request is required for it to implement the Controller class. Anyway to get around this? – dlite922 Jan 23 '17 at 02:53
  • @dlite922 I'm not sure what you're asking. – Eric Jan 23 '17 at 03:43
  • @Eric I have a listener for a queue. And currently it forks from inside the Controller. The child thread does not start until someone makes a GET call to "/" or any other. I'm trying to eliminate the need to do this GET call. Will this method work for that purpose? – dlite922 Jan 23 '17 at 17:01
  • If I understand correctly, this method will work for that purpose. If you try it and discover that it doesn't work, I think it would be better to post a separate question with a minimum working example rather than continuing the discussion in comments here. – Eric Jan 23 '17 at 17:38
  • Cool thanks @Eric. A question directly related to the above. Why do I need a trait here in this instance. Why can't I just have an implementation I want to bind as an eager singleton? – dlite922 Jan 23 '17 at 19:18