Compile time dependency injection in Play 2.4 with macwire

Post on 28-Jul-2015

1.151 views 0 download

Transcript of Compile time dependency injection in Play 2.4 with macwire

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?