Skip to main content
Ben Nadel at dev.Objective() 2015 (Bloomington, MN) with: Jake Morehouse
Ben Nadel at dev.Objective() 2015 (Bloomington, MN) with: Jake Morehouse ( @JacobMorehouse5 )

Building A Simple ColdFusion Dependency Injection Framework

By on
Tags:

My ColdFusion applications are not yet very component-intensive. I have some objects that do some stuff, but I have nothing that approaches a fully modelled environment. As such, I have not yet felt the "pain" that so many people seem to feel in their applications. But after attending CFUNITED, I thought it would be fun to play with some sort of dependency injection framework. And, if there's anything more fun than playing with a framework, it's building one. And so, this morning, I took an hour and put together this very simple dependency injection factory.

After taking Luis Majano's one day ColdBox class (which was phenomenal by the way), I decided that I wanted to try building an annotation-based dependency injection framework in which the dependency information was defined right in the ColdFusion components. For this simple experiment, I have two annotations available:

<cfcomponent ioc:singleton="true" />

This ioc:singleton attribute in the CFComponent tag determines if the target class should be cached inside of the dependency injection factory as a singleton.

<cfproperty name="Foo" ioc:class="Bar" />

This ioc:class attribute in the CFProperty tag tells the dependency injection factory to get a handle on the class "Bar" and inject it into the target component using the private variable name, "Foo."

When you need a ColdFusion component created, you can get it from the dependency injection factory and it will take care of introspecting the target class meta data and injecting all of the annotated dependencies. In my experiment, the factory will execute the constructor for singletons as well as for transients (when allowed). It will not, however, pass any arguments into the target constructor. If you have a class that requires constructor arguments, you can get around this limitation in one of two ways; for singletons, you can simply create them manually before you create the Factory and pass them in during Factory initialization (at which time they will be cached). For transients, you can ask that the Factory inject the dependencies but not call the transient constructor, allowing the calling context to receive an uninitialized object.

This functionality is not meant to be robust - it's just a thought experiment. People seem to worship ColdSpring and I wanted to get a taste of why. So, now that we have the general idea, let's take a look at my experiment. I created a set of user-related objects meant to create and save user data (in an extremely fake and minimal way). Let's work from the ground up, first looking at my DSN object. The DSN object is just a standard ColdFusion component:

DSN.cfc

<cfcomponent
	output="false"
	hint="I am a DNS object.">

	<cffunction
		name="init"
		access="public"
		returntype="any"
		output="false"
		hint="I return an initialized object.">

		<!--- Define arguments. --->
		<cfargument
			name="source"
			type="string"
			required="true"
			hint="I am the data source."
			/>

		<cfargument
			name="username"
			type="string"
			required="true"
			hint="I am the username."
			/>

		<cfargument
			name="password"
			type="string"
			required="true"
			hint="I am the password."
			/>

		<!---
			Simply append arguments to THIS scope for testing.
			Normally, we would put them in the private scope,
			but I don't want to be bothered to create getters.
		--->
		<cfset structAppend( this, arguments ) />

		<!--- Return this object reference. --->
		<cfreturn this />
	</cffunction>

</cfcomponent>

There's nothing special here. This is one of those singleton components that needs constructor arguments (DSN information) and will therefore need to be created outside of the dependency injection framework.

Next, let's take a look at my data access layer, the UserDAO.cfc:

UserDAO.cfc

<cfcomponent
	output="false"
	hint="I am a user data access object."
	ioc:singleton="true">

	<!--- Define dependencies to be injected. --->
	<cfproperty name="dsn" ioc:class="DSN" />


	<cffunction
		name="init"
		access="public"
		returntype="any"
		output="false"
		hint="I return an initialized object.">

		<!--- Return this object reference. --->
		<cfreturn this />
	</cffunction>


	<cffunction
		name="save"
		access="public"
		returntype="string"
		output="false"
		hint="I am just returning a string to demontsrate the IoC properties.">

		<!--- Define arguments. --->
		<cfargument
			name="user"
			type="any"
			required="true"
			hint="I am the user being saved."
			/>

		<cfreturn "Saving user [#arguments.user.getName()#] to data source [#variables.dsn.source#]." />
	</cffunction>

</cfcomponent>

Here, things get a bit more interesting. Notice that the CFComponent tag for the UserDAO.cfc has the ioc:singleton attribute. This will tell the dependency injection framework to create a single instance of this class and cache it (as a singleton). Notice also that we have a single CFProperty tag telling the dependency injection framework to inject an instance of the DSN class. Other than that, there is only a Save() method which references the injected DSN instance.

This data access layer will be called by our service layer, UserService.cfc:

UserService.cfc

<cfcomponent
	output="false"
	hint="I am a user service."
	ioc:singleton="true">

	<!--- Define dependencies to be injected. --->
	<cfproperty name="userDAO" ioc:class="UserDAO" />


	<cffunction
		name="init"
		access="public"
		returntype="any"
		output="false"
		hint="I return an initialized object.">

		<!--- Return this object reference. --->
		<cfreturn this />
	</cffunction>


	<cffunction
		name="save"
		access="public"
		returntype="string"
		output="false"
		hint="I return the result of the save() method called on my injected DAO object.">

		<!--- Define arguments. --->
		<cfargument
			name="user"
			type="any"
			required="true"
			hint="I am the user being saved."
			/>

		<!--- Pass command to injected DAO object. --->
		<cfreturn variables.userDAO.save( arguments.user ) />
	</cffunction>

</cfcomponent>

Our service layer is very similar to our data access layer in that it is a singleton and has injected properties. However, rather than having a DSN object injected, the CFProperty in our UserService.cfc is telling the dependency injection framework to inject an instance of the UserDAO class. And, just as with our UserDAO, this injected property is being referenced in the Save() method.

This service layer will be called by our User object as part of an active record type architecture:

User.cfc

<cfcomponent
	output="false"
	hint="I am a user object.">

	<!--- Define dependencies to be injected. --->
	<cfproperty name="userService" ioc:class="UserService" />


	<cffunction
		name="init"
		access="public"
		returntype="any"
		output="false"
		hint="I return an initialized object.">

		<!--- Define arguments. --->
		<cfargument
			name="name"
			type="string"
			required="true"
			hint="I am the name of the user." />

		<!--- Save the properties. --->
		<cfset variables.name = arguments.name />

		<!--- Return this object reference. --->
		<cfreturn this />
	</cffunction>


	<cffunction
		name="getName"
		access="public"
		returntype="string"
		output="false"
		hint="I return the name variable.">

		<cfreturn variables.name />
	</cffunction>


	<cffunction
		name="save"
		access="public"
		returntype="string"
		output="false"
		hint="I return the result of the save() method called on my injected Service object.">

		<!--- Pass command to injected Service object. --->
		<cfreturn variables.userService.save( this ) />
	</cffunction>

</cfcomponent>

Unlike the other components, the User.cfc is a transient. This is why there is no ioc:singleton attribute in its CFComponent tag. And, because it is a transient, it requires specific constructor arguments. Make note of this as it will come into play in the next chunk of code. But, transient status of not, this component still has one defined dependency - the UserService class - which it will need in the Save() method.

I hope you are getting the picture so far: the User object has a save method which calls the UserService object which has a save method which calls the UserDAO object which has a save method which references the DSN object. We have all these dependencies being injected behind the scenes. Now, let's take a look at the code that makes use of all of this auto-wiring:

<!---
	Create a DSN instance. Since this requires more manual
	construction, create it before we create our IoC dependency
	injection factory.
--->
<cfset dsn = createObject( "component", "cfc.DSN" ).init(
	source = "myDataSource",
	username = "username",
	password = "password"
	) />

<!--- Create our factory and pass in our DSN object. --->
<cfset factory = createObject( "component", "cfc.Factory" ).init(
	DSN = dsn
	) />

<!---
	Get our user object. Since our user transient requires
	more information for the constructor, do not let the
	IoC factory call the constructor.
--->
<cfset user = factory.get(
	class = "User",
	callConstructor = false
	) />

<!--- Manually call the constructor on our user object. --->
<cfset user.init( name = "Tricia" ) />

<!--- Output the results of the save() method. --->
<cfdump var="#user.save()#" />

In this demo, we need to create an instance of our DSN object first because it requires manual initialization. Once it has been created, we can then create an instance of our dependency injection Factory, passing in the collection of pre-initialized singletons (the DSN object in our case). With the Factory in place, we then get a new User.cfc instance. Notice that when I am getting it from the Factory, I am telling the factory not to call the constructor method on the target class. I need to do this because I want to call init() on the User object manually with custom constructor data. Then, once I get the User.cfc instance, I initialize it and then save it. And, when we run this code, we get the following CFDump output:

Saving user [Tricia] to data source [myDataSource].

Notice that this is the output generated by the UserDAO.cfc. In order for this output to present on the screen as a result of User::save(), the User.cfc would need to be injected with a UserService.cfc instance, which would need to be injected with a UserDAO.cfc instance, which would need to be injected with a DSN.cfc instance. So, how does this all happen? Let's take a look at the Factory.cfc to see where the dependency injection is taking place:

Factory.cfc

<cfcomponent
	output="false"
	hint="I create objects by introspecting their properties and injecting dependencies using setter injection prior to calling object constructors (NOTE: Target objects should NOT assume dependencies are available during constructor methods in the case of circular references).">


	<cffunction
		name="init"
		access="public"
		returntype="any"
		output="false"
		hint="I return an intiailized object.">

		<!--- Define arguments. --->
		<!---
			N pre-initialized argumnets will be passed in to be
			stored in the cache. We are doing this so that objects
			too complex (or those requireing default information)
			can be prepared manually.
		--->

		<!---
			Create a cache area. This is where fully initialized
			objects will be stored. It is from this cache that
			outside pages will be able to pull singletons.
		--->
		<cfset variables.cache = {} />

		<!---
			Create a meta data area. This is where we will store
			the meta data for requested objects such that we don't
			need to keep gathering information for our transients.
		--->
		<cfset variables.metaData = {} />

		<!---
			Append the arguments to the cache. These are the
			pre-initialized singletons manually created by the
			application.
		--->
		<cfset structAppend( variables.cache, arguments ) />

		<!--- Return this object reference. --->
		<cfreturn this />
	</cffunction>


	<cffunction
		name="get"
		access="public"
		returntype="any"
		output="false"
		hint="I return an object of the given class. I will check for a singleton first before I try to create a new object.">

		<!--- Define arguments. --->
		<cfargument
			name="class"
			type="string"
			required="true"
			hint="I am the class to be retreived."
			/>

		<cfargument
			name="callConstructor"
			type="boolean"
			required="false"
			default="true"
			hint="I am flag as to whether or not to call the constructor method of the target object. This only applies to transient object so that the calling page can retain the right to call the constrcutor."
			/>

		<!--- Define the local scope. --->
		<cfset var local = {} />

		<!---
			Check to see if this object exists in our holding area.
			Notice that we are NOT looking in our pending
			collection as that is reserved for transients, which
			we don't want to recycle.
		--->
		<cfif structKeyExists( variables.cache, arguments.class )>

			<!--- Return this singleton. --->
			<cfreturn variables.cache[ arguments.class ] />

		</cfif>


		<!---
			ASSERT: If we have gotten this far, then the user
			has asked for an object that we don't yet have a
			reference to (or is a transient object that will need
			to be re-created each user request).
		--->


		<!---
			Check to see if we don't already have the component
			meta data. We are going to be caching it to cut down
			on file reads.
		--->
		<cfif !structKeyExists( variables.metaData, arguments.class )>

			<!--- Get and cache the component meta data. --->
			<cfset variables.metaData[ arguments.class ] = getComponentMetaData(
				arguments.class
				) />

		</cfif>


		<!--- Get the meta data for the given class. --->
		<cfset local.metaData = variables.metaData[ arguments.class ] />


		<!---
			Create an instance of the target class but do not
			initialize it yet. We want to add any IoC properties
			first.
		--->
		<cfset local.target = createObject( "component", arguments.class ) />

		<!---
			Create a default flag for singleton status. This
			will be used later on to determine if we should
			call the constructor.
		--->
		<cfset local.isSingleton = false />

		<!---
			Check to see if this target is a singleton. If so,
			throw it into the cache in case we need to build it
			up using circular references.
		--->
		<cfif (
			structKeyExists( local.metaData, "ioc:singleton" ) &&
			isBoolean( local.metaData[ "ioc:singleton" ] ) &&
			local.metaData[ "ioc:singleton" ]
			)>

			<!--- Cache this uninitialized singleton. --->
			<cfset variables.cache[ arguments.class ] = local.target />

			<!--- Turn on singleton status flag. --->
			<cfset local.isSingleton = true />

		</cfif>


		<!---
			Inject our tunneling method (which allows us to store
			values into the private scope of the target instance).
		--->
		<cfset local.target[ "$inject" ] = this.injectDependency />

		<!---
			Check to see if there are any properties that need
			to be injected.
		--->
		<cfloop
			index="local.property"
			array="#local.metaData.properties#">

			<!---
				Check to see if this property is one that we need
				to inject.
			--->
			<cfif structKeyExists( local.property, "ioc:class" )>

				<!---
					Inject our dependency. This call will
					recursively execute our get() method to
					find / create an instance of the target
					IoC property.
				--->
				<cfset local.target.$inject(
					name = local.property.name,
					value = this.get( local.property[ "ioc:class" ] )
					) />

			</cfif>

		</cfloop>

		<!---
			At this point, we have built our object completely so
			we can remove our tunneling method.
		--->
		<cfset structDelete( local.target, "$inject" ) />


		<!---
			Now that we have injected all of our dependencies,
			we can initialize the object. Check to see if we have
			an init method and that we should call the
			constructor. Remember, we are only going to do this
			for singletons OR for transients that have the call
			constructor flag set.
		--->
		<cfif (
			structKeyExists( local.target, "init" ) &&
			(
				local.isSingleton ||
				arguments.callConstructor
			))>

			<!--- Call constructor. --->
			<cfset local.target.init() />

		</cfif>

		<!--- Return the requested target. --->
		<cfreturn local.target />
	</cffunction>


	<cffunction
		name="injectDependency"
		access="public"
		returntype="void"
		output="false"
		hint="I am injected into a component to create an IoC injection tunnel.">

		<!--- Define arguments. --->
		<cfargument
			name="name"
			type="string"
			required="true"
			hint="I am the name of the property being injected."
			/>

		<cfargument
			name="value"
			type="any"
			required="true"
			hint="I am the property value being injected."
			/>

		<!---
			Inject dependency. Remember, at the time of execution,
			this method will be INSIDE another object altogether.
			Therefore, the context will NOT be the factory.
		--->
		<cfset variables[ arguments.name ] = arguments.value />

		<!--- Return out. --->
		<cfreturn />
	</cffunction>

</cfcomponent>

The code behind this is surprisingly simple. When you request a class from the Factory, it checks to see if it has it cached. If so, it returns it, if not, it creates it. When it has to create it, though, that's where things get interesting. After the Factory creates the target class, it checks the component meta data for two things: is it a singleton and does it have dependencies? If it is a singleton, it caches it immediately so that the instance can be used in circular references. It then checks to see if any dependencies are required by scanning the component properties for the ioc:class attribute. If it finds a dependency, it creates it, calling its own Get() method recursively, and then injects it using a method-based injection tunnel.

This little experiment doesn't do a whole lot. But, I can see how even this small, annotation-based dependency injection would be really useful in development. Like I said before, my current applications are not very CFC-intensive, so I am not sure where the real "pain" that people are feeling comes from. Anyway, this was fun to build and has piques my interesting. Perhaps I will look at ColdBox and ColdSpring to see how they work under the hood.

Want to use code from this post? Check out the license.

Reader Comments

7 Comments

Nicely done! I'm definitely going to try out the annotations for myself as I haven't done it before but have seen it in use by you and by others. It looks incredibly simple with really all that data coming from a call to getComponentMetaData. I worked on expanding Joe Rinehart's BeanFactory, I now have a 200 some line ObjectFactory that can handle any sort of dependency via constructor argument or property setter. Big difference though is that it uses XML and you can define values for those properties/arguments in XML (arrays, structs, simple values, etc.). I now will definitely be expanding it to support annotations as it looks incredibly easy.

15,674 Comments

@Javier,

The nice thing about XML is that you can define arguments that are not supplied by the DI framework (such as with my DSN data). To get around that in the annotation experiment, I had to supply those manually. I am not sure how to get around this without a centralized config document. Perhaps some config information can be put into the Factory to handle this.

10 Comments

@Ben,

You could define it in code; you could have a config object that acts as a container for the static config data. This is how guice and spring manage the non-xml style of configuration in java.

15,674 Comments

@Rich,

Yeah that's true. But, one of the nice things about the annotation style is that there is no additional code required. Having the config, however, would allow the Factory to mix arguments of type manual and auto-wired. Perhaps I can experiment with this.

28 Comments

That's an interesting take on the Active Record pattern. Most of the time, I would've expected the User object to either extend an ActiveRecord or have a reference to something similar (Reactor?), rather than having a reference to a service that then talks to a DAO to perform the CRUD.

15,674 Comments

@Tony,

I don't really know much about the active record pattern. I wasn't really trying to implement Active Record - I just wanted a string of objects that were dependent. I just threw out the term "active record" because the User object has a save() method.

28 Comments

@Ben,

Yeah I figured it was more for demonstration purposes than anything, but I've never thought about setting it up like that and it was kinda nice to see a new perspective on things. Props.

15,674 Comments

@Tony,

I've used this type of thing a few times in experimentation and I usually get told it's actually a "man in the middle" anti-pattern. I would be interested in seeing how it is done traditionally.

10 Comments

A few months ago I fiddled around with the same concept - amazing how close we are in implementation!

I create my factory on appStart and on its initialization it recursively searches my root for all .cfc files, collects meta data, adds those marked 'singleton' to its cache and then works on transients similar to yours. Even with a lot of objects, it's a minor hit to auto-search and even then only when restarting an app but then you're up flying!

5 Comments

As someone said "The beauty is lack of everything needless", so your ColdFusion applications are very useful in terms of beauty

34 Comments

Hi Ben

I know that you are moving ahead with a lot of new CF9 features and / or different methodologies / frameworks.

Please could you explain why you would use this whole Factory approach as aopposed to something like:

<cfif NOT StructKeyExists( application, 'appInitialized' )>
<cfset application.DSN = CreateObject('component','components.DSN').Init("datasource","username","password") />
<cfset application.userService = CreateObject('component','components.userService').Init(application.DSN) />
<cfset application.appInitialized = true />
</cfif>
<cfset myUser = CreateObject('component','components.user').Init("Tricia") />
<cfset application.userService.Save(myUser) />

This is just a quick crude example, but as far as I see it does the same? Is there something additional that the Factory approach does code like above doesn't?

If you run through it quickly, it basically does this:

If no cached application classes are found, then the DSN, and UserService classes are instantiated. (The UserService.Init method in turn instantiates a reference to the UserDAO object and calls its Init method)

You then simply create an instance of a User, and use the cached application.UserService to save that instance.

What does this factory approach offer that I am missing - because I know there is something :)

Cheers

28 Comments

@Paulo,

Your example does work for smaller applications, but once you get into a larger project with multiple objects, each have various dependencies between to other objects, it becomes rather cumbersome to define all the relationships in your application. Using a dependency injection framework, like ColdSpring, helps make object creation easier by centralizing object creation inside a factory, where dependencies can be managed a lot cleaner using convention-based annotations or an XML file.

@Will,

You're correct in that Ben's example and ColdSpring both deal with dependency injection. The big difference between the two is that Ben's example uses annotations within the components to define the dependencies whereas ColdSpring uses an external XML file to wire everything together.

15,674 Comments

@Andy,

I like that, sounds like a good idea. I think we are going in the same direction - mine just uses a "just in time" type meta data read, you use an up-front cache. 6 of one, half a dozen of other.

@AndreyM,

Thanks!

@Paolo,

To be honest, my traditional Factory object does just what you say; I hard code my object definitions and cache them manually in the Factory constructor. The only difference really is that if I need to add a new CFC to the application, I would have to go in and update the code in the Factory; however, if I was using annotation, all I would have to do is ask the Factory for the new CFC and it would know how to create it.

This difference is rather minor, in my opinion, and simply saves you from having to manually wire CFC creations. People seem to think of CFC creation as some complicated art, but is it not really; even with things like circular references, a simple create / cache / initialize work flow will easily deal with this.

2 Comments

Ben, I'm glad I ran across this posting. I've been a big fan of annotation-based configuration for some time and have been working on just such a framework ever since the CF 9 beta was released (aside from automatic dependency injection, it also provides AOP facilities and rest web services support). I've used annotated components on a limited basis with CF 8 - but am really enjoying the new freedom that the enhanced script of CF 9 provides - which is what inspired the new direction. In any case, the purpose of my response isn't to shamelessly promote the framework, just to let you know that I should be posting it in it's alpha release state to Riaforge within the next 24 hours or so.

15,674 Comments

@Joshua,

Yeah, ColdFusion is really awesome in the way you can really quickly put a method into another object and then rip it right out again. Dynamic languages FTW :)

15,674 Comments

@Erwin,

That is not specific to a version of ColdFusion. You can add your own custom attributes to CFComponent, CFProperty, CFFunction tags. It is considered best practice to name-space the custom attributes so that they don't ever accidentally collide with news creative native attributes:

<cfproperty namespace:attribute="value" />

... example:

<cfproperty ioc:singleton="true" />

In this case, the namespace is "ioc" (or Inversion of Control) and the attribute itself is, "singleton."

8 Comments

Thank you Ben. This brought a whole lot of clarity to what was happening in this example. Now I have one thing I'm trying to understand. $inject. what does it mean to use the $ with the keyword inject. Is this an undocumented feature of ColdFusion?

15,674 Comments

@Erwin,

The use of the "$" in the function handle is just to help ensure that you don't accidentally overwrite any existing method in the ColdFusion component. You could have used something like "_" as well (I think).

8 Comments

Just to point out, in practice you'd need to recursively iterate through the metadata of any components a requested object extends, to pick up any dependencies defined at that level.

1 Comments

Thank you for showing how you can use custom attributes on the component function, argument, and property tags. That allowed me to quickly build my own code that in the end was very similar to yours even though I didn't read your Factory.cfc in detail. I handled also handled sending in custom arguments via argumentCollection=. The argumentCollection= way was very simple way to handle custom arguments for the init function of each object. Here is an example of how to do that from outside the factory:

local.argCol=structnew();
local.ts=structnew();
local.ts.color="white";
local.argCol["vehicle"]=local.ts;
local.ts=structnew();
local.ts.cylinders=6;
local.argCol["engine"]=local.ts;
local.magicCar=local.ioc.resolve("vehicle", local.argCol);

The argCol keys are the component names. I put them all in the same struct so that this struct can be sent recursively and used for any depth you require.

and inside the factory:

if(structkeyexists(arguments.argumentsCollection, arguments.class) EQ false){
local.newObject.init();
}else{
local.newObject.init(argumentCollection=arguments.argumentsCollection[arguments.class]);
}

I excluded my other code to keep it simple.

The comment from @Dave about recursively checking "extends" was very useful as well since I overlooked that.

3 Comments

Great article!

I was wondering what the difference is in this example and object extension?

For instance the dao extending the dsn object

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel