This document discusses dependency injection (DI) at compile time versus runtime in Play applications using Macwire for DI. It provides an overview of using Macwire for compile-time DI by wiring dependencies through their types. This avoids runtime overhead and ensures the dependency graph is checked at compile time. The document demonstrates wiring a complex dependency tree and using Macwire interceptors. It also discusses integrating Macwire with Play 2.4 so that DI and routing are checked at compile time.
5. Compile time DI
●
With the cake pattern
– Talk “Structure your Play application with the cake
pattern (and test it)”
●
Slides: http://de.slideshare.net/yann_s/play-withcake-export2
●
Video: http://www.ustream.tv/recorded/42775808
●
With constructor parameters
6. DI with constructor parameters
class Dependency1
class Dependency2 {
def parse(input: String): Unit =
println(s"parse '$input' with '$this'")
}
class Service(dep1: Dependency1, dep2: Dependency2) {
def parse(input: String): Unit = dep2.parse(input)
}
val dep1 = new Dependency1
val dep2 = new Dependency2
val service = new Service(dep1, dep2)
7. Singleton or not
// singleton
val dep1 = new Dependency1
val dep2 = new Dependency2
val service = new Service(dep1, dep2)
// one instance per call
val dep1 = new Dependency1
def dep2 = new Dependency2
def service = new Service(dep1, dep2)
8. val or lazy val
val dep1 = new Dependency1
val service = new Service(dep1, dep2)
val dep2 = new Dependency2
java.lang.NullPointerException
lazy val dep1 = new Dependency1
lazy val service = new Service(dep1, dep2)
lazy val dep2 = new Dependency2
9. Complex dependency tree
lazy val dep1 = new Dependency1
lazy val dep2 = new Dependency2
lazy val dep3 = new Dependency3
lazy val dep4 = new Dependency4
lazy val dep5 = new Dependency5(dep3, dep4)
lazy val dep6 = new Dependency6(dep2, dep4)
lazy val dep7 = new Dependency7(dep5, dep6)
lazy val service = new Service(dep1, dep2, dep3, dep4, dep7)
10. With macwire
import com.softwaremill.macwire._
lazy val dep1 = wire[Dependency1]
lazy val dep2 = wire[Dependency2]
lazy val dep3 = wire[Dependency3]
lazy val dep4 = wire[Dependency4]
lazy val dep5 = wire[Dependency5]
lazy val dep6 = wire[Dependency6]
lazy val dep7 = wire[Dependency7]
lazy val service = wire[Service]
And that's all!
13. Integration of macwire with Play
●
Play 2.3
– Everything checked at compile time, expect...
routing... :(
●
Play 2.4
– Everything checked at compile time!
– https://github.com/yanns/TPA/pull/1/files
●
Demo
14. Macwire interceptor
●
Ex: monitor performance of ws calls:
– MonitoringInterceptor
lazy val videoGateway: VideoGateway = logDuration(wire[VideoGateway])
lazy val logDuration = MonitoringInterceptor.logDuration
[debug] duration - 15ms for gateways.VideoGateway#top()
[debug] duration - 5ms for gateways.PlayerGateway#findPlayer(2)
[debug] duration - 7ms for gateways.PlayerGateway#findPlayer(1)
[debug] duration - 4ms for gateways.PlayerGateway#findPlayer(3)
[debug] duration - 23ms for services.TopVideoService#topVideos()
15. Integration of macwire with Play 2.4
●
build.sbt:
libraryDependencies ++= Seq(
"com.softwaremill.macwire" %% "macros" %
"1.0.1",
"com.softwaremill.macwire" %% "runtime" %
"1.0.1")
routesGenerator :=
play.routes.compiler.InjectedRoutesGenerator
●
Customer loader in
application.conf:
play.application.loader=globals.TBAApplicationLo
ader
●
Custom loader:
package globals
import controllers.Assets
import play.api.ApplicationLoader.Context
import play.api._
import play.api.libs.ws.ning.NingWSComponents
import play.api.routing.Router
import router.Routes
class TBAApplicationLoader extends ApplicationLoader {
override def load(context: Context): Application = {
Logger.configure(context.environment)
(new BuiltInComponentsFromContext(context) with
TBAComponents).application
}
}
trait TBAComponents
extends BuiltInComponents // standard play components
with NingWSComponents // for wsClient
with TBAApplication {
import com.softwaremill.macwire._
lazy val assets: Assets = wire[Assets]
lazy val router: Router = wire[Routes] withPrefix "/"
}
16. Test application for IT tests
val context = ApplicationLoader.createContext(
new Environment(new File("."), ApplicationLoader.getClass.getClassLoader, Mode.Test))
class TBAApplicationLoaderMock extends ApplicationLoader {
override def load(context: Context): Application = {
new BuiltInComponentsFromContext(context) with TBAComponents {
override lazy val wsClient: WSClient = MockWS(SimulatedPlayerBackend.routes)
}.application
}
}
implicit val application = new TBAApplicationLoaderMock().load(context)
val server = TestServer(9000, application)
running(server) {
WsTestClient.withClient { ws ⇒
val response = await(ws.url(s"http://localhost:9000/player/$playerId").get())
response.status shouldEqual OK
}
}