dsug 05 02-15 - scaldi - lightweight di in scala

30
ScalDI: Lightweight DI in Scala [email protected]

Upload: ugo-matrangolo

Post on 16-Jul-2015

4.346 views

Category:

Software


0 download

TRANSCRIPT

Page 1: Dsug 05 02-15 - ScalDI - lightweight DI in Scala

ScalDI: Lightweight DI in Scala

[email protected]

Page 2: Dsug 05 02-15 - ScalDI - lightweight DI in Scala

Inversion Of Control

● The Hollywood Principle "Don't call us, we'll call you"

● You relinquish control on when your code gets executed to an external framework

○ Windowing system○ Callbacks○ High-Order functions

Page 3: Dsug 05 02-15 - ScalDI - lightweight DI in Scala

Inversion Of Controldef service(s: String): Future[String] = future {

s.toUpperCase}

val foo = service(“foo”)val FOO = Await.result(foo, Duration.Inf)println(FOO + “ - bar”)

service(“foo”).onSuccess {case res => println(res + “ - bar”)

}Not IoC: full control of program flow

IoC: scala.concurrent._ will run your code

Page 4: Dsug 05 02-15 - ScalDI - lightweight DI in Scala

Dependency Injection

● A form of IoC where you relinquish the object creation/initialisation logic

class Manager {val serviceOne = new ServiceOneImpl()val serviceTwo = new ServiceTwoImpl()

def tx() {val id = serviceOne.fetch()serviceTwo.store(id)// ...

}}

object ManagerFactory {val Instance = new Manager(

new ServiceOneImpl(),new ServiceTwoImpl()

)}

class Manager(private val serviceOne: ServiceOne,private val serviceTwo: ServiceTwo

) {def tx() { … }

}

Page 5: Dsug 05 02-15 - ScalDI - lightweight DI in Scala

Dependency Injection

● Writing Factories is boring● IoC containers

○ Spring Framework ○ Google Guice ○ Google Dagger○ “The Cake Pattern” ○ ScalDI

Page 6: Dsug 05 02-15 - ScalDI - lightweight DI in Scala

The Cake Pattern● Advertised as the Scala way to do DI natively without

any external framework● Based on the concept of “self-type”

○ Constraints an impl of a type to be also an impl of the defined self types

trait Foo {self: Bar with Baz =>// the Foo type is going to assume that it is also a Bar and a Baz// ...

}

Page 7: Dsug 05 02-15 - ScalDI - lightweight DI in Scala

The Cake Pattern

trait Shell {}trait Engine {}

trait Car {self: Shell with Engine =>

}

trait RenaultShell extends Shell {}trait NissanEngine extends Engine {}trait NissanShell extends Shell {}

class RenaultScenic extends Car with RenaultShell with NissanEngine {}class NissanQashqai extends Car with NissanShell with NissanEngine {}

Page 8: Dsug 05 02-15 - ScalDI - lightweight DI in Scala

The Cake Pattern

● First introduced by Odersky as the main modularization tool for the scalac/scala toolset [1]

● Further evangelized by Boner on his blog [2]● Easy cookbook on the Cake Solution blog [3]

Page 9: Dsug 05 02-15 - ScalDI - lightweight DI in Scala

The Cake PatternLet’s use it to build a simple app with this structure:

UserService

UserDao

MongoDB PostGres

Page 10: Dsug 05 02-15 - ScalDI - lightweight DI in Scala

The Cake PatternLike:● No need of a IoC framework● All your deps are checked at compile timeDon’t like:● Lots of boilerplate code● On complex layouts is hard to see what the deps of a module

are● Introduces ‘hard’ concepts of the Scala type system● Hard to get it right● All these traits will slow down compilation time

Page 11: Dsug 05 02-15 - ScalDI - lightweight DI in Scala

The Cake Pattern

Truth is that people will start to give up on it pretty soon shortcutting to abominations like:

trait MyServiceImpl extends MyService with DependencyAwith DependencyBwith DependencyCwith DependencyDwith DependencyEwith DependencyFwith DependencyG...

Page 12: Dsug 05 02-15 - ScalDI - lightweight DI in Scala

The Cake PatternCurrent Production code in GILT:

object Application extends Journal with Autocomplete with ProductLookSearch with SaleSearch with CategorySearch with AgeAndGenderSearch with HealthCheck with RuntimeEnvironment with Prefetch

Page 13: Dsug 05 02-15 - ScalDI - lightweight DI in Scala

The Cake Patterntrait RuntimeEnvironment extends Environment override val search = ??? override val searchEventPublisher = ??? override val users = ??? override val sales = ??? override val ruleSets = ??? override val productLooksCache = ??? override val productDetailsCache = ??? override val externalProductsCache = ??? override val taxonomiesCache = ??? override val skuJournalsCache = ??? override val publicBrandsCache = ??? override val ruleSetsCache = ??? override val salesCache = ??? override val brandPromotions = ??? override val personalizer = ??? override val kafkaUtil = ??? override val benefitsCache = ??? override val hopperSales = ??? override val redirectClient = ??? override val preferences: Preferences = ??? // … more !!

Who is depending on what ?

Page 14: Dsug 05 02-15 - ScalDI - lightweight DI in Scala

The Cake Patterntrait SaleSearch extends SearchController with ResponseHelper

with RedirectHelper with PilHelper

with HopperHelper with SaleHelper with ExternalProductViewHelper

with PageComponentsHelper with TopOfFunnelHelper with AbTestHelper {

self: Environment =>

// WTF! // Stuff popping out from nowhere}

● On what SaleSearch really depends on?

● We basically flattened the dependency graph!!

Page 15: Dsug 05 02-15 - ScalDI - lightweight DI in Scala

The Cake Pattern● Hard to get it right even for Odersky & Co.● Just happened on scala-internals ML:

https://groups.google.com/forum/#!search/scala-internals$20EECOLOR/scala-internals/Z0kV6iDam0c/-09cDMEz754J

“I do have some opinions about the trait you linked. I think it's design is flawed. Take for example it's base definition:

trait Typechecker extends SymbolTable with Printers with Positions with CompilationUnits with QuasiquotesImpl

When I read such a type my mind goes something like this:

So Typechecker is a SymbolTable with Printers and Positions and CompilationUnits and QuasiquotesImpl. That instantly sends me to a place that I don't like….”

“Here we have a peculiar consequence of using the cake pattern. Scalac's codebase illustrates this on a number of occasions as well.”

Page 16: Dsug 05 02-15 - ScalDI - lightweight DI in Scala

The Cake Pattern

// in Implicits.scalatrait Implicits { self: Analyzer =>

// in Analyzer.scalatrait Analyzer extends AnyRef with Contexts with Namers with Typers with Infer with Implicits with EtaExpansion with SyntheticMethods with Unapplies with Macros with NamesDefaults with TypeDiagnostics with ContextErrors with StdAttachments with AnalyzerPlugins

Page 17: Dsug 05 02-15 - ScalDI - lightweight DI in Scala

The Cake Pattern

● No control over initialization logic○ Is not part of the DI definition but is a nice to have○ With Cake you need to code it yourself○ Could be problematic due to trait linearization

● Let’s see how it could happen ...

Page 18: Dsug 05 02-15 - ScalDI - lightweight DI in Scala

ScalDI● IMHO the Cake pattern is

○ too much hassle○ hard to get it right○ badly implemented could cripple your code base

● Alternatives?● Be disciplined and write your factory objects● ScalDI

○ Small and simple○ Easier to use and to understand

Page 19: Dsug 05 02-15 - ScalDI - lightweight DI in Scala

ScalDISimple: only 3 concepts● Injector

○ A container for your bindings● Module

○ A place where you define the bindings using a nice DSL

○ It is an Injector● Injectable

○ Provides a DSL to inject dependencies

Page 20: Dsug 05 02-15 - ScalDI - lightweight DI in Scala

ScalDISimple example

class DefaultServiceImpl(implicit inj: Injector) extends Service with Injectable { val conf = inject [String] (identified by “service.configuration”) def run() = { println(s”conf: $conf”) }}

class ServiceModule extends Module { binding indentifiedBy “service.configuration” to “jdbc://mysql/mydb” bind [Service] to new DefaultServiceImpl // bind [Service] toNonLazy DefaultServiceImpl }

class MyApp(implicit inj: Injector) extends Injectable {private val service: Service = inject [Service]

}

Page 21: Dsug 05 02-15 - ScalDI - lightweight DI in Scala

ScalDIModule composition

val storeModule = new Module {bind [UserDao] to new PsqlUserDaoImpl()

bind [OrderDao] to new MongoDbOrderDaoImpl()}

val cacheModule = new Module {bind [UserCache] to new LRUUserCacheImpl()bind [OrderCache] to new TTLOrderCacheImpl()

}

val appModule = storeModule :: cacheModule// then in test ...val mocks = new Module {

bind [OrderDao] to new MockOrderDaoImpl()}val appModule = mocks :: appModule // will redefine OrderDao with a mock

Page 22: Dsug 05 02-15 - ScalDI - lightweight DI in Scala

ScalDIConstructor injection

class ServiceImpl(dao: Dao, cache: Cache) extends Service { // … }

val module = new Module {bind [Service] to injected [ServiceImpl]

}

// after macro expansion

val module = new Module {bind [Service] to new ServiceImpl(

dao = inject[Dao],cache = inject[Cache]

)}

Page 23: Dsug 05 02-15 - ScalDI - lightweight DI in Scala

ScalDIYou can inject everything!

val myModule = new Module {binding identifiedBy “greetings” to “Hello World!”binding identifiedBy “adder” to ((a: Int, b: Int) => a + b)

}

class Consumer(implicit inj: Injector) extends Injectable {val greet: String = inject [String] (identifiedBy “greetings”)val adder = inject [(Int, Int) => Int] (identifiedBy “adder”)

}

Page 24: Dsug 05 02-15 - ScalDI - lightweight DI in Scala

ScalDIConditional injection

val daoModule = new Module {bind [Dao] when (inDevMode or inTestMode) to new H2Dao()bind [Dao] when to new PsqlDao()

def inDevMode = !inTestMode && !inProdModedef inTestMode = System.getProperty(...)def inProdMode = System.getProperty(...)

}

Page 25: Dsug 05 02-15 - ScalDI - lightweight DI in Scala

ScalDILifecycle!

trait Cache { def start()def stop()

}class CacheImpl extends Cache {

def start() = { fetchAll() }def stop() = { … }

}

val app = new Module { // will init all your bindingsbind [Cache] to new CacheImpl()

initWith { _.start() }, destroyWith { _.stop() }

}app.destroy() // will invoke all your destroy logics

Page 26: Dsug 05 02-15 - ScalDI - lightweight DI in Scala

ScalDI + PlayCan inject into Play controllers!

// with ScalDI a Controller is not an Object!!// just remember to add a @ in the routes fileclass MyController(implicit inj: Injector) extends Controller with Injectable {

private val serviceOne = inject [ServiceOne]private val serviceTwo = inject [ServiceTwo]

def index = Action {Ok(serviceOne.run(serviceTwo.run()))

}}

Page 27: Dsug 05 02-15 - ScalDI - lightweight DI in Scala

ScalDI + AkkaInject Props and ActorRef

class ParentActor(implicit inj: Injector) extends Actor with AkkaInjectable {val childActorProps = injectActorProps [ChildActor]val friendActorRef = injectActorRef [FriendActor]

def receive = {case Spawn => context.actorOf(childActorProps)case Greet => friendActorRef ! Hello

}}

Page 28: Dsug 05 02-15 - ScalDI - lightweight DI in Scala

ScalDI + Play = DEMO!

Page 29: Dsug 05 02-15 - ScalDI - lightweight DI in Scala

ScalDII like:● Easy● Nice DSL to inject/define dependencies● Different forms of injections● Handles lifecycle● Supports Play and AkkaDon’t like:● Still somewhat intrusive

Page 30: Dsug 05 02-15 - ScalDI - lightweight DI in Scala

EOF