compile time dependency injection in play 2.4 with macwire
TRANSCRIPT
DI in Play 2.4Compile time
Dependency Injection
with macwire
Yann Simon
Dependency Injection
RuntimeVS
Compile Time
Runtime vs compile time DI
● Runtime✔ Support Lifecycle (start / stop)
● Compile time✔ Dependency graph checked by compiler✔ No runtime overhead
source:http://apmblog.compuware.com/2013/12/18/the-hidden-class-loading-performance-impact-of-the-spring-framework/
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
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 Dependency1val dep2 = new Dependency2val service = new Service(dep1, dep2)
Singleton or not
// singletonval dep1 = new Dependency1val dep2 = new Dependency2val service = new Service(dep1, dep2)
// one instance per callval dep1 = new Dependency1def dep2 = new Dependency2def service = new Service(dep1, dep2)
val or lazy val
val dep1 = new Dependency1val service = new Service(dep1, dep2)val dep2 = new Dependency2
java.lang.NullPointerException
lazy val dep1 = new Dependency1lazy val service = new Service(dep1, dep2)lazy val dep2 = new Dependency2
Complex dependency tree
lazy val dep1 = new Dependency1lazy val dep2 = new Dependency2lazy val dep3 = new Dependency3lazy val dep4 = new Dependency4lazy 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)
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!
macwire
● Macwire resolves dependencies based on thetype– Compile error when ambiguous– No good idea to have a String as dependency ;)
Macwire
● https://github.com/adamw/macwire● Other features
– Accessing wired dynamically– Interceptors– Qualifiers
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
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()
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 inapplication.conf:
play.application.loader=globals.TBAApplicationLoader
● Custom loader:package globals
import controllers.Assetsimport play.api.ApplicationLoader.Contextimport play.api._import play.api.libs.ws.ning.NingWSComponentsimport play.api.routing.Routerimport router.Routes
class TBAApplicationLoader extends ApplicationLoader { override def load(context: Context): Application = { Logger.configure(context.environment) (new BuiltInComponentsFromContext(context) withTBAComponents).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 "/"}
Test application for IT testsval 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 }}
The End
Questions?