« Back
in DDD dependencyinjection cfc coldfusion read.

Dependency Injection with ColdFusion.

Bringing old imperative spaghetti code written by many developers of varying skill levels up to snuff is tough business. I'm currently working with some old ColdFusion code and want to bring it up to date. For anyone else having to do the same thing I hope this helps you.

So let's talk about Dependency Injection for ColdFusion.

Just as a quick recap, introducing DI will give us the following benefits:

  1. Allow us to implement a service layer / persistence layer so that we don't have <cfquery> tags littered throughout our code.
  2. Have behavioural control over the components by implementing interfaces
  3. Make code testable.
  4. Swap out implementations of services without affecting other pieces of code.

Let's say we wanted to have a cfm page display a bunch of image links from our movies media collection. How would we go about architecting this?

Let's prepare some directories in our application first.

//This will contain our core models/entities/domain objects
Application/Core/Domain/*         

//Our interfaces that determine the behaviour
Application/Core/Services/*        

//Our transient concrete implementations of the services above. More on this later
Application/Core/Services/beans    

Preparing The Behaviour Interfaces

First, we want to define the behaviour that our services will implement. So let's do this through an interface called Application/Core/Services/IMovieImageRepository.cfc

interface{  
    public string function getImageById(required numeric id);
}

Concrete Implementation of IMovieImageRepository

Now we need something that implements the previous interface Application/Core/Services/beans/MovieLinkDAO.cfc:

component implements="Application.Core.Services.IMovieImageRepository"{

    public string function getImageById(required numeric id){
        //Do some querying here or use an ORM
        //I've hard coded it here as an example
        return "http://mydomain.com/image.jpg";
    }
}

If we don't have the getImageById function, any page will give us an error immediately before we even use the repository. That's because the glue that holds everything together will be instantiated at the Application.cfc or Application.cfm startup. We will get to that in a bit.

As mentioned earlier we put this instance in the /beans folder. The reason for this is because the dependency injection framework we're using Di/1, searches the /beans folders for any components that we use and puts them into Transient scope. All concrete implementations underneath the beans folder will be instantiated per use. If we specify one level up, all those concrete implementations will be in Singleton scope.

So just to recap Di/1's folder configuration to search for components:

  • Application/Core/Services would be Singleton scope.
  • Application/Core/Services/beans would be in Transient scope.

The reason why I chose Di/1 is because it's still having commits and is used in other MVC frameworks for ColdFusion like FW/1. Additionally, only one file has to be added to the application: ioc.cfc and it has tests.

Now if we're working with an old code base, we can pull out the queries everywhere that is fetching image links and put it in this component. If the team prefers to do it with good old SQL for speed optimizations they can or use an ORM or any other means. It also means putting the same code in one place so that improvements only need to be made once. If the data fetching was something quite complicated, other developers don't have to worry about it and we don't risk them making a mistake by trying to do it themselves (assuming they're new).

Tying It All Together

In Application/Application.cfm or Application.cfc, depending on how new your CF version is we've got something similar to this:

<!---Add Dependency Injection Component--->  
<cfset application.beanFactory=createObject("component","ioc").init(folders=expandPath('/Application/Core/Services'))/>

<cfscript>    application.beanFactory.addAlias("imageRepository","MovieLinkDAO");  
</cfscript>  

or Application.cfc:

function onApplicationStart(){  
    application.beanFactory = new ioc(folders=expandPath("/Application/Core/Services"));
     application.beanFactory.addAlias("imageRepository","MovieLinkDAO");
    }

We've kind of made an alias here. Normally in other languages we'd find all instances of the interface IMovieImageRepository and replace it with a MovieLinkDAO but alas this is ColdFusion which is kind of type safe but not really.

Instead any component that uses the name imageRepository will get an instance of MovieLinkDAO.

Because this is all running at application startup, if the components didn't exist, or they didn't implement our intended behaviour, we'd get an immediate ColdFusion error, nice. I realize that some people don't have a full continuous integration process and probably don't compile their code. So having that immediate error is a good thing.

Testing Our Movie Link Provider with DI

Alright so to make a test page we go ahead and make Application/Movies/index.cfm:

<cfset movieController=createObject("component", "movieController").init()/>  
<cfoutput>  
    #movieController.test()#
</cfoutput>  

Inside Application/Movies/movieController.cfc we've got:

component{  
    property type="IMovieImageRepository" name="_imageRepository";

    public movieController function init(){
        _imageRepository=application.beanFactory.getBean("imageRepository");
        return this;
    }

    public string function test(){
        return _imageRepository.getImageById(1);
    }

}

Because our IoC container was put into application scope at application.beanFactory, we can call it anywhere. Di/1 has a method on it to fetch us the concrete implementation of the alias.

By making a property in the component to have type IMovieImageRepository, we are enforcing that behaviour and nothing else can slot into that property. When we call application.beanFactory.getBean(), it will return our concrete implementation specified in the Application.cfm or Application.cfc file. In this case it would be MovieLinkDAO.

Running Application/Movies/index.cfm gives us

http://mydomain.com/image.jpg

Great! Ideally though our DAO object should be fetching domain entities from whatever persistent storage it's using. Our entities should be sitting inside Application/Core/Domain.

As another thought, in the Application.cfc what we could do is make an if statement to see if it's in "debugging mode". If it is, we can replace all our implementations of MovieLinkDAO.cfc with something that returns arrays or structs instead so that we can perform testing with an in memory database. That way we can expect consistency and not have tests that would pass one moment but fail the next.

This also makes it harder for new developers to come in and break the existing code base.

This is my first foray into ColdFusion, any constructive criticism is certainly welcome in the comments below.

Thanks for reading!

comments powered by Disqus