Scala Dependency Injection in Play Framework

user3685285

I am new to Scala, the PlayFramework, and Dependency Injection. I downloaded the sample scala play framework code. Can someone explain to me why we need to inject the Clock and the appLifeCycle? It is referenced above, so there is no need to inject it right? What is going on here? Why do we need to do this for Web Frameworks in general?

package services

import java.time.{Clock, Instant}
import javax.inject._
import play.api.Logger
import play.api.inject.ApplicationLifecycle
import scala.concurrent.Future

/**
 * This class demonstrates how to run code when the
 * application starts and stops. It starts a timer when the
 * application starts. When the application stops it prints out how
 * long the application was running for.
 *
 * This class is registered for Guice dependency injection in the
 * [[Module]] class. We want the class to start when the application
 * starts, so it is registered as an "eager singleton". See the code
 * in the [[Module]] class to see how this happens.
 *
 * This class needs to run code when the server stops. It uses the
 * application's [[ApplicationLifecycle]] to register a stop hook.
 */
@Singleton
class ApplicationTimer @Inject() (clock: Clock, appLifecycle: ApplicationLifecycle) {

  // This code is called when the application starts.
  private val start: Instant = clock.instant
  Logger.info(s"ApplicationTimer demo: Starting application at $start.")

  // 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 { () =>
    val stop: Instant = clock.instant
    val runningTime: Long = stop.getEpochSecond - start.getEpochSecond
    Logger.info(s"ApplicationTimer demo: Stopping application at ${clock.instant} after ${runningTime}s.")
    Future.successful(())
  }
}
Eric

I assume you're using Lightbend's Play Scala Seed, which contains the code sample that you posted.

If you look at the documentation for java.time.Clock, you'll notice it says (emphasis mine):

Best practice for applications is to pass a Clock into any method that requires the current instant. A dependency injection framework is one way to achieve this. {.. code sample omitted .. } This approach allows an alternate clock, such as fixed or offset to be used during testing.

Ultimately the purpose of dependency injection is to allow you to define the interface that you want to inject into a class or an object, and configure the implementation of that interface in just one spot. The alternative is having to update a hardcoded dependency in multiple files, which can be messy and error prone. In the Play Scala Seed project, you'll notice a file called app/Module.scala. This file is one place where you can configure the bindings and they'll automatically be bound on application start. Notice the line where we bind the Clock implementation:

class Module extends AbstractModule {

  override def configure() = {
    // Use the system clock as the default implementation of Clock
    bind(classOf[Clock]).toInstance(Clock.systemDefaultZone)
    // Ask Guice to create an instance of ApplicationTimer when the
    // application starts.
    bind(classOf[ApplicationTimer]).asEagerSingleton()
    // Set AtomicCounter as the implementation for Counter.
    bind(classOf[Counter]).to(classOf[AtomicCounter])
  }

}

This configuration is saying "when my application starts up, wherever I've injected a Clock should use Clock.systemDefaultZone." If you want ApplicationTimer to use a different clock during tests, you might do something like this:

import play.api.{Environment, Mode}

// Notice that we include the environment
class Module(environment: Environment) extends AbstractModule {
  override def configure() = {
    // Use the system clock as the default implementation of Clock
    environment.mode match {
      case Mode.Prod | Mode.Dev => {
        bind(classOf[Clock]).toInstance(Clock.systemDefaultZone)
      }
      case Mode.Test => {
        // Specifically use UTC clock in tests, because maybe it's easier to write assertions that way
        // You could inject anything here and the ApplicationTimer would use it during tests
        bind(classOf[Clock]).toInstance(Clock.systemUTC())
      }
    }
    bind(classOf[ApplicationTimer]).asEagerSingleton()
    bind(classOf[Counter]).to(classOf[AtomicCounter])
  }
}

You can define modules in other places in the root package (i.e. no package com.example.whatever declaration at the top of the file) and they'll be automatically loaded, too. Otherwise, you'll need to add a binding in conf/application.conf that adds your module's name to the play.modules.enabled key. You can see a commented-out example of that in the Play Scala Seed. I also go into more depth in another answer that I've written here.

As for ApplicationLifecycle, this is a special module provided by Play that you could override with your own binding, but I'm not sure why you'd want to. It's there to give you access to hooks that execute before the application shuts down. Again, this component is injected because it would be simple to swap it out. Imagine having 100 modules that all depend on the application lifecycle. By default, it's bound to DefaultApplicationLifecycle (you can see it being bound here). If you had hardcoded DefaultApplicationLifecycle in all 100 modules, you'd have to update each one if you wanted to switch to a different lifecycle. With dependency injection, you just need to configure the binding to use a different lifecycle and the 100 modules will automatically use it.

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

Dependency Injection (DI) in Scala with Play

Play framework dependency injection is not working

Play Framework without dependency injection?

Play Framework PathBindable with Dependency Injection

Scala Play Guice Dependency Injection Fails

Play Framework: Dependency Injection in Sub-Templates

Scala Play framework dependency in clean sbt project

Scala Play test can't find WSClient during dependency injection

Play Framework 2.5 dependency injection in form object bean (Java)

Dependency injection with abstract class and object in Play Framework 2.5

ScalaWS in play framework 2.5 and dependency injection of WSClient in custom class

Play framework compile-time dependency injection and singleton

Play Framework 2.4 - Dependency injection to replace GlobalSettings.onStart()

Play framework java dependency injection - when to use singletons

Play Framework dependency injection Object vs @Singleton Class

Dependency injection with Play Framework and Guice leads to NullPointerException on Application start

Python Dependency Injection Framework

Entity Framework Dependency Injection

Dependency injection and Entity Framework

Dependency Injection framework

Scala play Guice injection

(Play 2.4) Dependency injection in a trait?

Composition of Readers for Dependency Injection in Scala

Constructor Dependency Injection in Spring Framework

Dependency Injection Slim Framework 3

Dependency Injection Does not work in Models or Tests in Play Framework 2.4.x

Scala PLAY Guice Injection and Implicits

play 2.5 DB issue - Use dependency injection

Configuration a Play dependency injection module as a trait