Chapter 4. Automated Dependency Injection

You may well have heard of Dependency Injection—there’s a certain buzz around the term that has been moving through the ActionScript community for the last couple of years. It’s one of those terms that sounds like it must be really sophisticated and complex, but actually turns out to be a fancy name for a simple concept that you already understand how to use.

This doesn’t mean that it’s not powerful and interesting, but, as with most design patterns, it’s the neat capturing of an idea that many programmers encounter on their own into a single specific term—‘Dependency Injection’—that is most useful. You were probably already doing it; now you’ll have a more pithy way of referring to it.

So, what exactly is Automated Dependency Injection?

First of all, it’s worth knowing that Dependency Injection—also known as DI—is a complicated name for something you’ve been doing since the first time you passed a parameter to a function.

A dependency is just a requirement to use another object

If the UserXMLLoader class needs to be passed a loadScriptPath:String of the url from which to load its data, this is a dependency:

public function UserXMLLoader( loadScriptPath:String )

You can fulfil a dependency in three different ways

When an instance of one class needs to use an instance of another class, you can support this relationship (fulfil the dependency) in various ways:

  1. You can create a new instance of a class within the object that is dependent on it

  2. You can use the locator pattern to pull a pre-existing instance of the dependency into the class that is dependent on it. This decouples your dependent class from knowing how to construct the class it’s dependent on. But of course your class is now dependent on the locator too

  3. You can ‘manually inject’ it. Inject in this situation really just means ‘create it somewhere else and give it directly to the dependent object’

You already use Dependency Injection

Any time you do any of these, you’re using DI:

Constructor injection

public class UserXMLLoader
{
    // declares the dependency in the constructor
    public function UserXMLLoader( loadScriptPath:String )
    {
    }  
}

// the dependency is fulfilled at instantiation:
var userXMLLoader:UserXMLLoader = new UserXMLLoader(remoteScriptPath);

Public property injection

public class UserXMLLoader
{
    // declares the dependency as a public property
    public var loadScriptPath:String;    
}

var userXMLLoader:UserXMLLoader = new UserXMLLoader();

// dependency is fulfilled by setting a public property after instantiation:
userXMLLoader.loadScriptPath = remoteScriptPath;

Setter method injection

public class UserXMLLoader
{
    protected var _loadScriptPath:String;    

    public function UserXMLLoader( )
    {
    }

    // declares the dependency through a property setter
    public function setLoadScriptPath(value:String):void
    {
        _loadScriptPath = loadScriptPath;    
    }
}

// dependency is fulfilled by setting a protected property after instantiation
// via a setter method:
var userXMLLoader:UserXMLLoader = new UserXMLLoader();
userXMLLoader.setLoadScriptPath(remoteScriptPath);

There are different ways to inject dependencies

So, if we’re all doing DI already, why the buzz about it? The truth is that injecting dependencies is trivial in a small example, but quickly becomes tiresome in a large application—tending to result in a lot of ‘pass the parcel’ code where objects are holding on to properties they’re not really interested in, simply so that they can ‘inject’ them into other objects they create or interact with.

Configuration, cooperation and communication related dependencies are key to the responsibilities of the class—you could say that these are ‘real’ dependencies. Pass the parcel dependencies could be said to be ‘artificial’ dependencies. If objectA needs to use objectB, and objectB requires objectC for configuration, this imposes an artificial dependency (on C) on objectA.

This particular problem is the one we’re usually seeking to avoid when we resort to using statics and globals as property holders in our code, or use an object locator pattern. Solving dependency-chain problems in this way is really just a case of shuffling the problem from place to place, sacrificing something in return for each solution.

Statics and globals make code rigid, brittle, hard to test, and prone to memory leaks

Static properties and methods have their place—nobody would argue that a function that finds prime factors isn’t a good use of static—but when we use statics to hold state we make big sacrifices in other areas. We can easily use a static value to configure the UserXMLLoader, but now it’s hard to test how the class responds to connection failures or bad responses from the script. We also couple the UserXMLLoader to the AppConfig—when really it only wants to know about the script path.

public class UserXMLLoader
{
    protected var _loadScriptPath:String;    

    public function UserXMLLoader()
    {
        // pulls the dependency through a static reference
        _loadScriptPath = AppConfig.loadScriptPath;
    }
} 

public class AppConfig
{
    public static const loadScriptPath:String = 'http://sample.com/userXML.php';    
}

Locator patterns push extra responsibilities on your classes

Even if you avoid using a static instance for the object that can supply your classes with what they need, it’s still a big imposition on a class to require it to do not just its job but also know how to get all the things it needs to do its job.

It’s tempting to imagine that we can ‘just’ use a common base class to quickly roll this functionality into many classes across our codebase and keep it maintained in one place, but this is a really bad place to make inheritance decisions from! And what about inevitable common super/subclasses that don’t need this functionality but are part of the inheritance chain?

Surely there has to be a better way?

Automated DI gets around the need to ‘pass the parcel’, but keeps code flexible

The intention behind automated DI containers is to abstract the fulfilment of dependencies from the application itself. Essentially, we split this job out completely, so that the application code no longer has to do it, and instead we ask a third party—the DI container—to get it done.

Automated DI shifts the responsibility for fulfiling dependencies to a third party
Figure 4-1. Automated DI shifts the responsibility for fulfiling dependencies to a third party

This reduction of responsibilities for your own code is a plus, but there’s another advantage: being able to type your dependencies against interfaces instead of concrete types.

A getInstance() singleton always requires you to be dependent on a concrete class. For example, even if UserXMLLoader implements an IUserLoader interface, the XMLUserLoadingService has to use the actual class, and not the interface, in order to access the static getInstance method:

public class XMLUserLoadingService implements IUserLoadingService
{
   //We are stuck with the UserXMLLoader 'forever'
   private var userXMLLoader:IUserLoader = UserXMLLoader.getInstance();

   public function loadAllUsers():void
   {
       userXMLLoader.loadAll();
   }
}

With Robotlegs Automated DI, we can declare a particular dependency in a class, and configure our application (through our context) to know how to fulfil that dependency: which concrete class to inject against an interface for example. In this example, IUserLoader might be fulfilled by XmlUserLoader, JsonUserLoader or even DummyUserLoader:

public class LoadUserCommand
{
   //We can swap out our IUserLoader implementations easily.
   [Inject]
   public var userLoader:IUserLoader;

   public function execute():void
   {
       userLoader.loadAll();
   }
}

How does Robotlegs Injection work?

Automated DI is a handshake

Like any handshake, the Automated DI handshake has two sides:

  1. The injection point in the class (asking for something to be provided)

  2. The rule in the injector (defining how it should be provided)

Robotlegs injection has two sides
Figure 4-2. Robotlegs injection has two sides

From the developer’s point of view, that’s almost all there is to it. Injection point + injector rule = happy classes.

You can specify an injection point in three ways

Injection points (dependencies that you would like to be provided) can be specified as public properties, properties with a setter function, or through your class constructor.

// as a public property
[Inject]
public var someProperty:IPropertyType;

... or using a setter function
[Inject]
public function setSomeProperty(value:IPropertyType):void
{
    ...
}
... or in your constructor
[Inject]
public function SomeClass(someProperty:IPropertyType):void
{
    ...
}

Warning

The Automated DI container (the injector) has to be able to find out which dependencies your class would like to be injected. This means that injection points always have to be public—whether you’re defining them as properties, setter functions or through the constructor. The injector will silently ignore any private or protected injection point.

And you also have to tell the injector what you would like it to do

Any injection point in your application has to be paired with a rule that you’ve created about how to fulfil that dependency. You do that in your context using the Robotlegs Injector. For example, to declare a rule that anytime a class declares a dependency on IPropertyType you want to use a specific instance of UserURLParams, you’d use this:

injector.mapValue(IPropertyType, new UserURLParams("robotlegs.org"));

Robotlegs has different types of injection

An injection mapping is really just a rule about how to satisfy the dependency. It’s really no more complex than that. You’re saying “When a class asks for this, give it this.”

Robotlegs offers a choice of four rules about how the injector should respond to the request:

mapClass(SomeType, TypeA)

Meets SomeType requests with an instance of TypeA—using a fresh instance each time (note that it injects an instance of the class, not the class itself)

mapSingleton(SomeType)

Meets SomeType requests with the same instance of SomeType every time

mapSingletonOf(SomeType, TypeA)

Meets SomeType requests with an instance of TypeA—using the same instance each time

mapValue(SomeType, new TypeA())

Meets SomeType requests with the instance of TypeA provided—using the same instance each time

How the injector processes injection rules
Figure 4-3. How the injector processes injection rules

If you only want one instance, use mapSingleton

// in the context startup();
injector.mapSingleton(UserLoadingService);
// in the class that has the dependency:
[Inject]
public var userLoadingService:UserLoadingService;

Unlike the getInstance() Singleton pattern, the class itself—UserLoadingService—doesn’t need to know that it’s going to be used as a ‘singleton’—meaning that the injector will use the same instance every single time this dependency is requested. As well as freeing the UserLoadingService from the responsibility of maintaining its ‘singleness’, you are also now free to create additional instances if, for some reason, you needed to.

Neat!... but our code is now coupled to the actual implementation class. Our injection points declare UserLoadingService as their dependency. We can do better!

mapSingletonOf keeps your code coupled only to interfaces

// in the context startup();
injector.mapSingletonOf(IUserLoadingService, UserLoadingService);
// in the class that has the dependency:
[Inject]
public var userLoadingService:IUserLoadingService;

Now we’re free to switch the concrete UserLoadingService for a DummyUserLoadingService or LocalUserLoadingService in the context and we know that every single class that is dependent on the injected IUserLoadingService will get this kind of concrete instance instead.

What if my class has to be created elsewhere? (e.g. a factory)

mapValue lets you control the creation of the singleton instance:

// in the context startup();
injector.mapValue(IUserURLParams, new UserURLParams("Robotlegs.org");
// in the class that has the dependency:
[Inject]
public var userURLParams:IUserURLParams;

mapValue() works much like mapSingletonOf(), except that it’s your code, instead of the injector, that controls the creation of the instance that is provided to each object that has this dependency. How you create this instance is up to you, with all the usual options—new Thing(), factories, fluent builders[2]—available to you.

You can only create one rule per class

Automated Injection is limited to one rule per dependency class, in each context. (See, context is making more sense now, isn’t it?)

So, this would explode, because the injector has no way of telling which value to use for which injection:

// in the class that has the dependencies:
[Inject]
public var url:String;

[Inject]
public var username:String;
// in the context startup();
mapValue(String, 'Robotlegs.org');
mapValue(String, 'Joel Hooks');

Named rules let you create multiple rules for each class (but they’re icky)

The injector actually lets you map multiple injections against the same class, if you provide an additional parameter—a ‘name’ to tell the like injections apart.

// in the class that has the dependencies:
[Inject(name='weburl')]
public var url:String;

[Inject(name='username')]
public var username:String;
// in the context startup();
mapValue(String, 'Robotlegs.org', 'weburl');
mapValue(String, 'Joel Hooks', 'username');

We understand that this looks attractive initially. It means you can inject against base types without having to create custom classes. Which sounds like a plus, but the reliance on a String for identification is weak—with the possibility of runtime problems that are hard to test and debug if you accidentally use the wrong name.

// in the class that has the dependencies:
[Inject(name='usrname')]
public var username:String;

You need to tell the compiler to include the injection metadata

The Flash/Flex compiler will strip out non-native metadata unless you tell it not to – this includes the [Inject] and [PostConstruct] metadata that Robotlegs needs to function correctly. Sometimes, when building an AIR application for example, the metadata will stay intact while debugging but will be stripped out when you publish your release build.

You need to tell the compiler that you want it to keep the [Inject] and [PostConstruct] metadata tags.

The Robotlegs swc includes the required compiler arguments for you, but when linking against the source you will need to add the arguments yourself, and how you need to do that depends on what you’re using to compile your code:

FlashBuilder/FlexBuilder solution

In your project properties, under ‘Flex Compiler’, add the following to the ‘Additional compiler arguments’:

-keep-as3-metadata+=Inject -keep-as3-metadata+=PostConstruct
Including Robotlegs metadata in FlashBuilder
Figure 4-4. Including Robotlegs metadata in FlashBuilder

Flash CS4/CS5 IDE Solution

Flash CS4 and CS5 don’t offer the option to specify the metadata arguments to the compiler directly. For a while it was thought that they couldn’t be used with custom metadata at all—but it turns out that if you build against the Robotlegs swc, and you specify that you also want to build a swc of your project, the metadata stays in place. Nice!

Including Robotlegs metadata in Flash CS4 and CS5
Figure 4-5. Including Robotlegs metadata in Flash CS4 and CS5

IntelliJ Solution

In IntelliJ, specific compiler arguments are provided on a per-module rather than per-project basis. In the Flex Compiler Settings for your module, in the ‘Additional compiler options’ add:

-keep-as3-metadata+=Inject -keep-as3-metadata+=PostConstruct

Flex SDK Solution—command line compiling

Add the following to your compiler arguments:

-keep-as3-metadata+=Inject -keep-as3-metadata+=PostConstruct

Flex SDK Solution—compiling with ant

Add the following to your compiler arguments:

        <arg value="-keep-as3-metadata+=Inject"/>
        <arg value="-keep-as3-metadata+=PostConstruct" />

Automated Injection ‘Gotchas’

Automated DI is fairly simple once you get to grips with it, but there are a few common tripping points that can make your first experience with it frustrating if you’re not aware of them.

If an object has an [Inject]ed dependency you have to create it using the Injector.

Injection isn’t magic, it’s just a very neat way of abstracting some factory logic when your objects are instantiated.

If you do new ThingWithInjection() you’ll find that none of the Injections have been fulfilled. It doesn’t matter where you put this code, the injector has no idea you’ve created a new instance and so it can’t get busy injecting it. There are some tricks for manually instantiating objects with [Inject]ed dependencies in the power-ups section. In a typical implementation this won’t be necessary as all your classes with Injections will be created by the Injector.

You can map injection rules at runtime, but beware of race conditions.

If your rule isn’t mapped before the first time it needs to be used, you’ll get an injector error.

The injection point and rule have to be of exactly the same type

The Injector allows you to specify an injection point using an interface and then fulfil it with a concrete class, but you have to make sure the first parameter in your rule matches the injection point.

This won’t work:

// in the class file of the concrete type
public class SpecialThing implements IThing ...

// in the context
injector.mapSingletonOf(IThing, SpecialThing);
               
// in the class file where you declare the dependency to be injected
[Inject]
public var thing:SpecialThing    // this needed to be mapped to IThing

You also can’t substitute superclasses/subclasses, so this won’t work either:

// in the class file of the concrete type
public class ExtraSpecialThing extends SpecialThing ...

// in the context
injector.mapSingleton(ExtraSpecialThing);
               
// in the class file where you declare the dependency to be injected
[Inject]
public var thing:SpecialThing  // this needed to be mapped to ExtraSpecialThing
                               // but much better to use an interface here!

If you override a method that has an [Inject] tag, you need to add it in the subclass

If you extend a class with an [Inject] method and you override that method, the compiler will use the describe-type data from the subclass and not the superclass, so it won’t know about the original [Inject] tag. You need to tag the subclass method with [Inject] too. If you’re working on a class which includes methods with [Inject] tags that others are likely to override, give some thought to whether there’s a more fool-proof way to achieve the same result.

An advantage of methods over properties is that they can be declared in an interface, so if you do decide to keep your injected method for this reason, make sure you shout out the need for the [Inject] tag!



[2] A fluent builder uses a natural-language approach to building an instance with the properties you want. For example, var quiz:Quiz = new QuizBuilder().multipleChoice.withTitle('Robotlegs jeopardy') .withQuestionSet('robotlegs.xml').build();

Get ActionScript Developer's Guide to Robotlegs now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.