ScalDI is a lightweight dependency injection (DI) framework for Scala that provides an alternative to the "Cake Pattern" approach. It defines three main concepts - Injector, Module, and Injectable - that make DI easier to use and understand compared to the Cake Pattern. Modules are used to define bindings between types and implementations using a simple DSL. The Injectable trait allows dependencies to be injected into classes in a type-safe manner. ScalDI also supports features like conditional bindings, constructor injection, lifecycle management, and integration with Play and Akka.
2. 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
3. Inversion Of Control
def 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
4. 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() { … }
}
5. Dependency Injection
● Writing Factories is boring
● IoC containers
○ Spring Framework
○ Google Guice
○ Google Dagger
○ “The Cake Pattern”
○ ScalDI
6. 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
// ...
}
7. 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 {}
8. 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]
9. The Cake Pattern
Let’s use it to build a simple app with this
structure:
UserService
UserDao
MongoDB PostGres
10. The Cake Pattern
Like:
● No need of a IoC framework
● All your deps are checked at compile time
Don’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
11. 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 DependencyA
with DependencyB
with DependencyC
with DependencyD
with DependencyE
with DependencyF
with DependencyG
...
12. The Cake Pattern
Current Production code in GILT:
object Application extends Journal
with Autocomplete
with ProductLookSearch
with SaleSearch
with CategorySearch
with AgeAndGenderSearch
with HealthCheck
with RuntimeEnvironment
with Prefetch
13. The Cake Pattern
trait 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 ?
14. The Cake Pattern
trait 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!!
15. 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.”
16. The Cake Pattern
// in Implicits.scala
trait Implicits {
self: Analyzer =>
// in Analyzer.scala
trait 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
17. 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 ...
18. 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
19. ScalDI
Simple: 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
20. ScalDI
Simple 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]
}
21. ScalDI
Module 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
22. ScalDI
Constructor 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]
)
}
23. ScalDI
You 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”)
}
24. ScalDI
Conditional injection
val daoModule = new Module {
bind [Dao] when (inDevMode or inTestMode) to new H2Dao()
bind [Dao] when to new PsqlDao()
def inDevMode = !inTestMode && !inProdMode
def inTestMode = System.getProperty(...)
def inProdMode = System.getProperty(...)
}
25. ScalDI
Lifecycle!
trait Cache {
def start()
def stop()
}
class CacheImpl extends Cache {
def start() = { fetchAll() }
def stop() = { … }
}
val app = new Module { // will init all your bindings
bind [Cache] to new CacheImpl()
initWith { _.start() },
destroyWith { _.stop() }
}
app.destroy() // will invoke all your destroy logics
26. ScalDI + Play
Can inject into Play controllers!
// with ScalDI a Controller is not an Object!!
// just remember to add a @ in the routes file
class 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()))
}
}
27. ScalDI + Akka
Inject 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
}
}
29. ScalDI
I like:
● Easy
● Nice DSL to inject/define dependencies
● Different forms of injections
● Handles lifecycle
● Supports Play and Akka
Don’t like:
● Still somewhat intrusive