The no-framework Scala Dependency Injection Framework

Post on 29-Nov-2014

4.842 views 0 download

description

Using a DI framework/container may seem obvious. But when was the last time you considered *why* do you really need one? After all, "dependency injection" is just a fancy name for passing arguments to a constructor. In the talk we'll walk through some of the features of DI containers and see if we can replace them with pure Scala code. We'll start with "manual" DI, followed with using MacWire to generate the wiring code for us. Then we'll proceed to a no-framework scopes implementation (e. g. request or session), which are very useful in web applications. We will also discuss possibilities of adding interceptors using macros. And finally, we'll see how to use traits to create and compose modules (similar to the module concept known from Guice), which can be viewed as a simplified cake pattern. As MacWire heavily uses macros, as a bonus, I'll explain how Scala Macros work and when they can be useful.

Transcript of The no-framework Scala Dependency Injection Framework

11/12/2013 BuildStuff 2013 @adamwarskiAdam Warski

BuildStuff 2013

The no-framework Scala Dependency Injection

framework

@adamwarski11/12/2013 BuildStuff 2013

You don’t need anything special to do Dependency Injection

@adamwarski11/12/2013 BuildStuff 2013

We often over-complicate

@adamwarski11/12/2013 BuildStuff 2013

Who Am I?

❖ Day: coding @ SoftwareMill!

❖ Afternoon: playgrounds, Duplos, etc.!

❖ Evenings: blogging, open-source!

❖ Original author of Hibernate Envers!

❖ ElasticMQ, Veripacks, MacWire!

❖ http://www.warski.org

@adamwarski11/12/2013 BuildStuff 2013

What is Dependency Injection?

@adamwarski11/12/2013 BuildStuff 2013

What is DI?

class PresentPackager {!

! def wrap() {!

! ! new RibbonSelector().selectRandom()!

! ! …!

} !

}

@adamwarski11/12/2013 BuildStuff 2013

What is DI?

class PresentPackager(rs: RibbonSelector) {!

! def wrap() {!

! ! rs.selectRandom()!

! ! …!

} !

}

@adamwarski11/12/2013 BuildStuff 2013

Yes, DI is just using parameters

@adamwarski11/12/2013 BuildStuff 2013

Why?❖ Restrict the knowledge of the class

class PresentPackager ! (rs: RibbonSelector) { ! def wrap() { ! ! rs.selectRandom() ! ! … ! } }

class PresentPackager { ! def wrap() { ! ! new RibbonSelector() ! ! ! .selectRandom() ! ! … ! } }

@adamwarski11/12/2013 BuildStuff 2013

But still …

❖ We need to have the news somewhere

@adamwarski11/12/2013 BuildStuff 2013

Let’s create a DI container! a.k.a. framework

@adamwarski11/12/2013 BuildStuff 2013

DI in Java

❖ Many frameworks!

❖ Configuration via:!

❖ XML!

❖ annotations!

❖ Java

@adamwarski11/12/2013 BuildStuff 2013

What’s wrong with that?

❖ Do I really need a DI framework?

@adamwarski11/12/2013 BuildStuff 2013

Let’s go back …

❖ … and just use our host language!

❖ in this case, Scala!

❖ mapping DI framework concepts to native language constructs

@adamwarski11/12/2013 BuildStuff 2013

Manual DI!!

object PresentWrapper extends App {!

! val ribbonSelector = ! ! ! ! new RibbonSelector()!

! val wrappingPaperFeeder = ! ! ! ! new WrappingPaperFeeder()!

! val presentPackager = ! ! ! ! new PresentPackager( ! ! ! ! ! ribbonSelector, ! ! ! ! ! wrappingPaperFeeder)!

}

@adamwarski11/12/2013 BuildStuff 2013

Manual DI!!

object PresentWrapper extends App {!

! lazy val ribbonSelector = ! ! ! ! new RibbonSelector()!

! lazy val wrappingPaperFeeder = ! ! ! ! new WrappingPaperFeeder()!

! lazy val presentPackager = ! ! ! ! new PresentPackager( ! ! ! ! ! ribbonSelector, ! ! ! ! ! wrappingPaperFeeder)!

}

@adamwarski11/12/2013 BuildStuff 2013

MacWireimport com.softwaremill.macwire.MacwireMacros._!

object PresentWrapper extends App {!

! lazy val ribbonSelector = ! ! ! ! wire[RibbonSelector]!

! lazy val wrappingPaperFeeder = ! ! ! ! wire[WrappingPaperFeeder]!

! lazy val presentPackager = ! ! ! ! wire[PresentPackager]

}

@adamwarski11/12/2013 BuildStuff 2013

Side-note: Scala Macros

@adamwarski11/12/2013 BuildStuff 2013

Side-note: Scala Macros

❖ Scala code executed at compile time!

❖ Operate on trees!

❖ Can inspect the environment, generate code!

❖ the code is type-checked

@adamwarski11/12/2013 BuildStuff 2013

Side-note: Scala Macros❖ E.g. debug macro!

def debug(params: Any*) = macro debug_impl!

def debug_impl ! ! ! (c: Context)! ! ! (params: c.Expr[Any]*): c.Expr[Unit]!

!

debug(presentCount) ⟹!

! println(“presentCount = “ + presentCount)

@adamwarski11/12/2013 BuildStuff 2013

Side-note: Scala Macros

❖ Debug macro implementation!

import c.universe._!

val paramRep = show(param.tree) val paramRepTree = Literal(Constant(paramRep)) val paramRepExpr = c.Expr[String](paramRepTree)!

reify { println( ! paramRepExpr.splice + ! " = " + ! param.splice) }

@adamwarski11/12/2013 BuildStuff 2013

Side-note: Scala Macros

❖ MacWire!

def wire[T] = macro wire_impl[T]!

def wire_impl ! ! ! [T: c.WeakTypeTag]! ! ! (c: Context): c.Expr[T]

@adamwarski11/12/2013 BuildStuff 2013

MacWireimport com.softwaremill.macwire.MacwireMacros._!

object PresentWrapper extends App {!

! lazy val ribbonSelector = ! ! ! ! wire[RibbonSelector]!

! lazy val wrappingPaperFeeder = ! ! ! ! wire[WrappingPaperFeeder]!

! lazy val presentPackager = ! ! ! ! wire[PresentPackager]}

@adamwarski11/12/2013 BuildStuff 2013

Scopes

❖ How long will an object (instance) live?

@adamwarski11/12/2013 BuildStuff 2013

Singleton & dependent

object NorthPole extends App {!

! // Singleton!

lazy val santaClaus = wire[SantaClaus]!

!

! // Dependent!

def gnome = wire[Gnome]!

}

@adamwarski11/12/2013 BuildStuff 2013

Arbitrary scopestrait WebFrontEnd { ! lazy val loggedInUser = ! ! session(new LoggedInUser) ! def session: Scope }!

!

trait Scope { ! def apply(factory: => T): T }

@adamwarski11/12/2013 BuildStuff 2013

Arbitrary scopes

object MyApp extends WebFrontEnd { ! val session: Scope = ! ! ! ! new ThreadLocalScope()!

! val filter = new ScopeFilter(session) ! // bootstrap the web server ! // using the filter}

@adamwarski11/12/2013 BuildStuff 2013

Arbitrary scopes

class ScopeFilter(sessionScope: ThreadLocalScope) ! ! ! extends Filter {!

! def doFilter(request: ServletRequest) { ! ! sessionScope ! ! ! .withStorage(request.getSession()) {!

! ! ! request.proceed()!

! ! } ! } }

@adamwarski11/12/2013 BuildStuff 2013

Modules

❖ Pre-wired!

❖ Composable!

❖ Dependencies!

❖ Module per package?!

❖ Veripacks :)

@adamwarski11/12/2013 BuildStuff 2013

Modules

❖ Module: trait!

❖ Pre-wired: new, MacWire!

❖ Composable: extends/with!

❖ Dependencies: extends/with / abstract members

@adamwarski11/12/2013 BuildStuff 2013

Modules

trait PresentWrapper {!

! lazy val ribbonSelector = ! ! ! ! wire[RibbonSelector]!

! lazy val wrappingPaperFeeder = ! ! ! ! wire[WrappingPaperFeeder]!

lazy val presentPackager = ! ! ! ! wire[PresentPackager]!

}

@adamwarski11/12/2013 BuildStuff 2013

Modules

trait PresentFactory extends PresentWrapper {!

! lazy val teddyBearProvider = ! ! ! ! wire[TeddyBearProvider]!

! lazy val toyTrainProvider = ! ! ! ! wire[ToyTrainProvider]!

lazy val presentAssembly = ! ! ! ! wire[PresentAssembly]!

}

@adamwarski11/12/2013 BuildStuff 2013

Modules

trait HomeOfSanta {!

! lazy val santaClaus = wire[SantaClaus]!

! lazy val rudolf = wire[Rudolf]!

! lazy val fireplace = wire[Fireplace]!

!

def presentAssembly: PresentAssembly!

}

@adamwarski11/12/2013 BuildStuff 2013

Modulestrait PresentWrapper { … } trait PresentFactory extends PresentWrapper { } trait HomeOfSanta { … }!

!

object NorthPole ! extends PresentWrapper ! with PresentFactory ! with HomeOfSanta {!

! santaClaus.deliver()!

}

@adamwarski11/12/2013 BuildStuff 2013

Testing Santa’s Homeclass HomeOfSantaTest extends FlatSpec {!

! it should “deliver presents” in {!

! ! val mockPresentAssembly = …!

! ! new HomeOfSanta {!

!! lazy val presentAssembly = ! ! ! ! mockPresentAssembly }!

! ! …!

! }!

}

@adamwarski11/12/2013 BuildStuff 2013

Cake Patterntrait PresentPackagerModule {!

! class PresentPackager { ! ! def wrap() { ! ! ! ribbonSelector.selectRandom() ! ! ! … ! ! } ! }

! lazy val presentPackager = new PresentPackager() ! def ribbonSelector: RibbonSelector }

@adamwarski11/12/2013 BuildStuff 2013

Cake Pattern

val cake = new PresentPackagerModule ! with RibbonSelectorModule ! with WrappingPaperFeederModule ! with TeddyBearProviderModule ! with ToyTrainProviderModule ! with PresentAssemblyModule ! with … { }

@adamwarski11/12/2013 BuildStuff 2013

Other features

❖ Interceptors!

!

trait Chimney {!

! lazy val presentTransferer = ! ! transactional(wire[PresentTransferer])!

! def transactional: Interceptor!

}

@adamwarski11/12/2013 BuildStuff 2013

Other features

❖ Factories!

❖ a dedicated object or …!

!

trait PresentBoxer {!

! def box(size: Size) = wire[Box]!

}

@adamwarski11/12/2013 BuildStuff 2013

Other features

❖ Instance maps!

❖ for integrating e.g. with Play!

❖ Factories!

❖ In-method wiring!

❖ More coming, someday :)

@adamwarski11/12/2013 BuildStuff 2013

Summing up

❖ Reconsider using a framework!

❖ Native Scala gives a lot of power!

❖ use it!

❖ wisely!

❖ More flexibility (less constraints)

@adamwarski11/12/2013 BuildStuff 2013

Links

❖ http://www.warski.org!

❖ https://github.com/adamw/macwire!

❖ http://springsource.com/

@adamwarski11/12/2013 BuildStuff 2013

Thanks!

❖ Questions?!

❖ Stickers ->!

❖ adam@warski.org