#354 Dependency Injection

katox Sun 31 Aug 2008

I was going through the endless discussion on New Design for Constructors . There is a bunch of brilliant ideas but none seemed to click completely.

Java world seems to be moving towards DI in large systems. Yet, noone mentioned DI in a debate about object construction. I am quite surprised.

Quick and random thoughts: we need to construct objects somehow, so what do we use in Java:

  1. constructors - but mostly simple ones, usually full of boilerplate code like this.x = x; (hmm); one can't call super() or this() anywhere in constructor because it might access unititialized data but one can call virtual methods having the same problems (eh?); moreover there are initilization blocks which are sort of no-args pre-init constructor functions, there may be more than one and they have to be ordered (yuck!)
  2. factory methods - easy to grasp but they are static methods and thus don't go very well with polymorphism; when we build larger systems we tend to write more and more boilerplate code around factory methods; often we make factories and clutter the code just to be able to write some tests (ridiculous)
  3. static initialization - after going through classloading code and static initialization code of log4j and logback I consider this pure evil ;)
  4. initialization methods - to be able to force reinitialization and state verification some interfaces mandate init method; other classes which implement something like that must propagate the same method of initialization (oh my)
  5. initialization using DI

Step #5 is done by many Java frameworks to fix Java initialization which is complex but underpowered. And there are many: Spring, Nano, Pico, Guice, EJB3 di, ...

I will and a link to Bob's Lee Guice User Guide because it contains a really nice motivation "why DI" (and I also like the simplicity of his approach).

I found interesting that the prefered method of DI in Guice was constructor DI though it didn't allow to put other than injected parameters to the constructed object. Isn't that another confirmation that constructors are meant just to put the object into a defined state and defer the true initialization and checks to normal instance methods?

I'd like to hear what you guys think of DI in Fan? Are there any other constructs which allow you to avoid problems described at the beginning of the Guice Guide?

brian Mon 1 Sep 2008

I think Java community gets a little overzealous in over-abstracting things, so dependency injection is kind of a nebulous concept. There are a couple of existing features in Fan where you might use something like DI in Java:

In Fan the concept of a "service" is just a thread which overrides isService to return true. Services can be resolved with a type using a variety of mechanisms:

Thread.findService(SqlService.type)
Sys.ns[`/sys/service/sql::SqlService`]
`fan:/sys/service/sql:SqlServic`.get   // brand new

The Type Database can be used to search for types, usually with indexed facets. Remember that the fan runtime itself provides reflection for installed pods and types, this will totally change how you think about problems compared to Java.

Another technique is to bind things with Uris just like you do on the Web. Uris are used everywhere in Fan, and becoming even more powerful in the next build. Anywhere you need a named indirection, Fan uses a Uri - with a bunch of mechanisms to plug-in how a given Uri binds to a given object.

helium Mon 1 Sep 2008

Brian: Your post sounds like you prefere service locators over dependency injection.

I found interesting that the prefered method of DI in Guice was constructor DI though it didn't allow to put other than injected parameters to the constructed object. Isn't that another confirmation that constructors are meant just to put the object into a defined state and defer the true initialization and checks to normal instance methods?

Constructor injection works with immutable objects. But I don't realy understand what you're saying. DI just describes a way to provide a service to an object.

// Foo depends on three services X, Y and Z
class Foo {
    // hardwired
    ServiceX x := ServiceX()

    // service locator
    ServiceY y := serviceLocator.getServiceY()

    // dependency injection
    ServiceZ z
    new make(ServiceZ z)
    {
        this.z = z
    }
}

brian Mon 1 Sep 2008

To me the real abstraction is how I create a system by assembling various software components. In your example is there really a difference? I'd probably initialize each service by type, with the ability to override using a with-block:

class Foo 
{
  ServiceX x := Thread.findService(ServiceX#)
  ServiceY y := Thread.findService(ServiceY#)
  ServiceZ z := Thread.findService(ServiceZ#)
}

foo1 := Foo()
foo2 := Foo { z = externalZ }

katox Mon 1 Sep 2008

helium: you can't mix ServiceZ and additional constructor parameters if you are using DI ... the point is that nobody misses such a feature

brian: The things get more complex if you are dealing with class hierarchies (made by composition). You can't simply override a service hidden three layers deep in the class you are initializing (no matter if using with-block or a normal constructor) without exposing unnecessary details about your implementation.

excerpt from the Guide: Before we discovered dependency injection, we mostly used the factory pattern. In addition to the service interface, you have a service factory which provides the service to clients as well as a way for tests to pass in a mock service ... The client is simple enough, but the unit test for the client has to pass in a mock service and then remember to clean up afterwards... Also, if you forget to clean up after your test, other tests may succeed or fail when they shouldn't. Even worse, tests may fail depending on which order you run them in. Finally, note that the service factory's API ties us to a singleton aproach. Even if getInstance() could return multiple instances, setInstance() ties our hands. Moving to a non-singleton implementation would mean switching to a more complex API.

The dependency injection pattern aims in part to make unit testing easier. We don't necessarily need a specialized framework to practice dependency injection. You can get roughly 80% of the benefit writing code by hand... Writing factories and dependency injection logic by hand for every service and client can become tedious... Guice aims to eliminate all of this boilerplate without sacrificing maintainability.

In the real world, you often don't know an implementation class until runtime. You need meta factories or service locators for your factories. Guice addresses these problems with minimal effort... The idea of bootstrapping is fundamental to dependency injection...Your code should deal directly with the Injector as little as possible. Instead, you want to bootstrap your application by injecting one root object. The container can further inject dependencies into the root object's dependencies, and so on recursively. In the end, your application should ideally have one class (if that many) which knows about the Injector, and every other class should expect to have dependencies injected.

Guice uses a Module and Binder objects because in Java there is no central place where one can configure actual implementations to be used.

I have no direct experience with using TypeDatabase but if it allows to specify which class implements which interface or which class is actually bound to a subclass (Guice can override even class bindings) at runtime then it could be used in the same way as as Module in Guice. Instead annotations URI locators would be used.

I think it just might work (I have to play with it a bit before I will able to say more). Unlike annotation DI at also doesn't appear to collide with constructors or with-blocks.

brian Mon 1 Sep 2008

I think it depends on what you are trying to do. In a production application, such as this site the services typically are configured via a fand script. That is where we define the wisp, sql, and email services and how they are configured.

For test suites you have to setup real or dummy services.

So I would say the preferred mechanism is the Thread service mechanism. But certainly the type database opens up some very interesting possibilities since you can query installed types to make more complex binding decisions (which in fact flux does quite extensively).

helium Mon 1 Sep 2008

helium: you can't mix ServiceZ and additional constructor parameters if you are using DI ... the point is that nobody misses such a feature

What about this:

// scala
class Foo (x : Int)(implicit z : ServiceZ)
{ ... }

...

import ServiceSetupStuff._   // defines implicit value of type ServiceZ

...

var foo = new Foo(42)

katox Mon 1 Sep 2008

helium: I just mentioned this Guice limitation (not Scala limitation, maybe even some other Java DI frameworks can do something about it) because I found interesting that noone seemed to be requesting this feature.

Login or Signup to reply.