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?
Top Related