Scaldi

Lightweight Dependency Injection Library

Created by  Oleg Ilyenko  @easyangel

Only 3 Main Concepts

1  Injector

A container for the bindings

3  Injectable

DSL for injection

inject [SomeClass]
        
... or this one ...

inject [Database] (identified by 'remote and by default new Riak)
        

Injector  and  Injectable


class OfficialMessageService(implicit inj: Injector)
          extends MessageService with Injectable {

  val officialGreeting =
    inject [String] (identified by "greeting.official")

  def getGreetMessage(name: String) =
    s"$officialGreeting, $name!"
}
        
and Module of course

class UserModule extends Module {
  bind [MessageService] to new OfficialMessageService

  binding identifiedBy "greeting.official" to "Welcome"
}
        

Bindings

Lazy binding

bind [ActorSystem] to new ActorSystem
        
Non-lazy binding

bind [Server] toNonLazy new HttpServer
        
Provider binding

bind [Connection] toProvider inject[Database].getConnection
        

★ Something Special ★

Injector Composition


def tokenModule = new Module {
  bind [Tokens] to new TokenRepo(db = inject [Database])
}

def dbModule = new Module {
  bind [Database] to new Riak
}

def appModule = tokenModule :: dbModule
        
and then test it

def mocksModule = new Module {
  bind [Database] to new InMemoryDb
}

implicit val testModule = mocksModule :: appModule
        

Injector Types

  • Mutable Injectors
  • Immutable Injectors
  • Properties Injector
  • Play Configuration Injector
  • Create you own, it's easy

trait Injector {
  def getBinding(identifiers: List[Identifier]): Option[Binding]
  def getBindings(identifiers: List[Identifier]): List[Binding]

  // ...
}
        

Constructor  Injection


class TokenRepo(db: Database, metrics: Metrics) extends Tokens {
  // ...
}
        
injected macro

bind [Tokens] to injected [TokenRepo]
        
compiled to

bind [Tokens] to new TokenRepo(
  db = inject [Database],
  metrics = inject [Metrics])
        

Argument  Overrides


bind [Tokens] to injected [TokenRepo] (
  'db -> inject [Database] ('tokensOnly))
        
compiled to

bind [Tokens] to new TokenRepo(
  db = inject [Database] ('tokensOnly),
  metrics = inject [Metrics])
        

Generics

bind

binding identifiedBy "intAdder" to
  ((a: Int, b: Int) => a + b)

binding identifiedBy "mapping" to Map(
  "scala" -> "http://scala-lang.org",
  "play" -> "http://www.playframework.com",
  "akka" -> "http://akka.io"
)
        
inject

val intAdder = inject [(Int, Int) => Int]

val mapping = inject [Map[String, String]]
        

Conditions


class UserModule extends Module {
  bind [MessageService] when (inDevMode or inTestMode) to
    new SimpleMessageService

  bind [MessageService] when inProdMode to
    new OfficialMessageService
}
        

def inDevMode(implicit inj: Injector) = {
  val mode = inject [Mode]
  Condition(mode == Dev)
}
        

Binding Lifecycle


class MyModule extends Module {
  bind [ActorSystem] to
    ActorSystem("ScaldiExample") destroyWith (_.shutdown())

  bind [Database] to new Mongo initWith (_.start())
}
        

implicit val injector = new MyModule

// do stuff ...

injector.destroy()
        

Extensibility

  • Extend almost any part of the library
    • Injector
    • Condition
    • Binding and BindingWithLifecycle
    • Identifier - create your own and define how they match
  • Type classes
    • CanCompose - defines how Injectors can be composed together
    • CanBeIdentifier

Create  a  Controller

Finally you can develop with classes and not singleton objects.

class Application(implicit inj: Injector)
          extends Controller with Injectable {

  val messageService = inject [MessageService]

  def index = Action {
    Ok(views.html.index(
      messageService.getGreetMessage("Test User")))
  }
}
        

Then  Bind  it


class MyModule extends Module {
  binding to new Application

  bind [MessageService] to new OfficialMessageService
}
        

Define  Global


object Global extends GlobalSettings with ScaldiSupport {
  def applicationModule =
          new MyModule :: new SomeOtherModule
}
        

Configure  routes

Don't forget @

GET  /   @controllers.Application.index
        

Play  Extras

Bindings

inject [Application]
inject [Mode]
inject [Configuration]
          
Injectors

def applicationModule =
  new MyModule ::
    new ControllerInjector
          
Conditions

bind [Thing] when (inDevMode or inTestMode or inProdMode) to
  new SomeThing
        
All configuration properties are also available as bindings

inject [String] (identified by "db.host")
inject [Int] (identified by "db.port")
        

Akka  Injectable

  • injectActorProps
  • injectActorRef

class Receptionist(implicit inj: Injector)
    extends Actor with AkkaInjectable {

  val orderProcessorProps = injectActorProps [OrderProcessor]

  val priceCalculator = injectActorRef [PriceCalculator]

  def receive = {
    case PlaceOrder(userName, itemId, netAmount) =>
      val processor = context.actorOf(orderProcessorProps)
      // ...
  }
}
        

Actor Ref  Factory

  • Should be implicitly available
    • ActorContext (within an actor)
    • ActorSystem (outside of actor)
  • Creates and configures an actor
    • Setups correct actor hierarchy
  • Integrates with Scaldi via Props

Actor  Bindings

Don't forget to bind with toProvider

class OrderModule extends Module {
  bind [ActorSystem] to ActorSystem("ScaldiExample")
    destroyWith (_.shutdown())

  binding toProvider new Receptionist
  binding toProvider new OrderProcessor
  binding toProvider new PriceCalculator
}
        

Present  &  Future

scaldi.org

Play 2.4 & 3.0

  • Native DI mechanism
  • JSR 330

EOF

Thank you!

Questions?