Dependency Injection experiment


#1

Hi all,

I’m not sure if Kotlin needs such a thing, but I was playing around with something akin to Dependency Injection using lazy delegates.

// Make “lazy” refer to Delegates.lazy
import kotlin.properties.Delegates
fun lazy<T>(initializer: () -> T) = Delegates.lazy(initializer)


public open class StandardModule {
  open val typeCreator by lazy { TypeCreator() }
  open val typeMapper by lazy { TypeMapper() }
  open val commands: Set<Command> by lazy { setOf(CreateMap(typeMapper, typeCreator)) }
  open val engine by lazy { Engine(commands) }
}


public open class TestModule : StandardModule() {
  // Replace commands for testing
  override val commands by lazy { setOf(MapPut()) }
}


fun main(args: Array<String>) {
  StandardModule().engine.commands.forEach { println(it.name) } // Prints CreateMap
  TestModule().engine.commands.forEach { println(it.name) } // Prints MapPut
}

By using lazy properites, you can override the definitions of in things in subclasses (in the example above, the TestModule replaces the commands for the engine instance defined in the StandardModule).

It doesn’t do auto-wiring, but I’d argue that’s proabably a good thing.

Anyway, just thought I’d share the experiment.

Cheers,
Andrew


#2

Hi Andrew,

my first idea I had when looking at those new lazy delegates in Kotlin was also whether this can be made use of to get rid of Spring XML wiring boilerplate code that is hard to debug and sometimes hard to read (have to follow myriads of imports). Here is some very simple Spring wiring:

  <bean id="myBean" >   <property name="myVar" ref="someOtherBean"/>      </bean>

For the wiring to be done some reference to "someOtherBean" needs to be obtained. So you still need some kind of bean container that has a store with all the already created references and some wiring functionality. Lazy delegates alone won't do. Without lazy variables supported by the language you can still do something like this:

if(myVar == null) {   myVar = initMyVar() }

AFAIKS lazy delegates free you from having to write that kind of boilerplate code, but it doesn't free you from getting the wiring done somehow.

– Oliver


#3

I don't think you can ever get rid of a container.

You can create special “wiring” delegates that keep a reference to the container. It will still be shorter than Spring’s XML

One big downside is that: the container must be passed explicitly in this case.


#4

Hi Oliver,

I guess I’m quite content to ditch autowiring at present - it’s not much overhead to be explicit if you use constructors to include dependencies (and it scales fairly well using named parameters).

In your example the container would just be the enclosing class and “wiring” is done explicitly via the constructor.

class Container {
  val someOtherBean by lazy { SomeOtherBean() }
  val someBean by lazy { SomeBean(someOtherBean) }
}

Or a less abstract example might be something like

class Container(val p: Properties) {
  val dataSource by lazy { DbConnectionPool(DataSource(p[“db.url”], p[“db.user”], p[“db.password”]) }
  val myDao by lazy { MyDao(dataSource) }
  val otherDao by lazy { OtherDao(dataSource) }
  val myService by lazy { transactional(MyService(myDao, otherDao)) }
  
  // Add support for proxies …  
  fun <T> proxy(obj: T, interceptors: Set<MethodInterceptor>): T …
  val transactionInterceptor = TransactionInterceptor()
  fun <T> transactional(obj: T, interceptors: T = proxy(obj, setOf(transactionInterceptor))

}

If you really want auto-wiring, following Andrey's suggestion you could do something like this:

class Container {
  val someOtherBean: SomeOtherBean by wired(this)
  val someBean: SomeBean by wired(this)   
}

where wired is a delagate that uses reflection on the instance (this) to look up the field type and do the auto wiring.

wired would probably also need to accept an optional class argument for the implementation class and you’d need some kind of qualifier mechanism to resolve ambiguities.

It might be nice if Kotlin’s PropertyMetadata included a reference to the enclosing object, then you could get rid of the the container reference - I imagine it could be a useful thing for delegates in general (although it might be considered leaky).

Cheers,
Andrew


#5

FYI, I just had a closer look at the Delegates implementation as you do have a this reference:

class Delegate() {
  fun get(thisRef: Any?, prop: PropertyMetadata): String {
    return "$thisRef, thank you for delegating '${prop.name}' to me!"
  }

  fun set(thisRef: Any?, prop: PropertyMetadata, value: String) {
  println("$value has been assigned")
  }
}


So you should be able to implement DI as:

class Container {
  val someOtherBean: SomeOtherBean by wired
  val someBean: SomeBean by wired
}

The this reference opens up interesting possiblities.  e.g. casting this to a callback reference before binding to alllow things like interceptors to be added. Interesting … :slight_smile:

Cheers,
Andrew