Adding Todo Lists (Integrating ColdSpring into our Application) - ColdBox Series Part 8b
In the last post, we looked at how to create a view and how to use handlers to display that view and to receive user input from the view. Now we need to do something with that user input. Here is what we need to get done first:
- Create our todoService.cfc
- Set up ColdSpring to create our todoService object and inject its dependencies
- Instantiate our todoService object in our handlers so that we can start to do something useful
Wow! That's a lot. It may not seem like a lot, but it is. So let's get started. here is our doAddList() handler method so far.
<cffunction name="doAddList" access="public" returntype="void" output="false">
<cfargument name="Event" required="true" type="coldbox.system.beans.requestcontext" />
<cfdump var="#event.getCollection()#">
<cfabort>
</cffunction>
It doesn't do much. But it does demonstrate that our FORM scoped variables are being inserted into our event object's request collection.
So the first thing we need to do is instantiate our To-do Service object so that we can make requests of it.
Wait. What? You mean we don't have a To-do Service yet?
Creating the To-do Service
The application were are writing is going to use a service layer. Essentially, the service layer is used to provide an API to our model. Any communication with our model will be done through our service layer.Our To-do Service is going to start out VERY simple. In our /model/todos directory, let's create a file called todoService.cfc. It should look like this:
<cfcomponent displayname="To-do Service" output="false">
</cfcomponent>
That's simple enough, isn't it?
Now, our todoService has some dependencies. It cannot do it's job without access to the model object todoGateway. We could very easily just add an init() method to this object that creates the todoGateway object and insert it into the variables scope:
<cffunction name="init" access="public" returntype="model.todos.todoService">
<cfset variables.todoGateway = createObject("component", "model.todos.todoGateway") />
</cffunction>
This would work fine. And for an application this small, this is likely the best way to do it. I, personally, don't like this approach for 2 reasons.
- I do not like the fact that my service object needs to be aware of any other objects. Perhaps I am over-thinking some OO concepts, but if my object needs a dependency, I would rather provide it then to have it go looking for the object itself
- By hard-coding the dependency, I have made it impossible to pass in a different object to use as the todoGateway. While it may be unlikely that I would ever need to, it feels wrong to me to hard-code it.
Besides, we want to learn ColdSpring, don't we? So let's use ColdSpring to manage this dependency.
Setting up ColdSpring for our Application
In our /config directory, we need to create a coldspring.xml.cfm file. We could just call it coldspring.xml, but adding the .cfm adds an extra layer of security<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans default-autowire="byName">
<bean id="todoGateway" class="model.todos.todoGateway">
<property name="dsn"><value>todos</value></property>
</bean>
<bean id="todoService" class="model.todos.todoService">
<property name="todoGateway">
<ref bean="todoGateway" />
</property>
</bean>
</beans>
Our coldspring.xml.cfm file is a pretty simple one. They get much more complicated, but don't be intimidated. Soon, it will be second nature.
The DOCTYPE definition at the top is optional. Technically, for it to be wellformed XML document it needs to be there along with the <?xml> element. But the best part about including the DOCTYPE is that you then get tag insight in Eclipse when you are using the XML editor. (FYI, the file needs to be named coldspring.xml for this to work.)
First, we have our <beans> element. This is the base element for the coldspring XML file and can only exist once. It will wrap ALL of the bean definitions. This element has an attribute called default-autowire which is currently set to "byName". This means that when ColdSpring tries to inject dependencies, it will do it by looking for a setter method with the name set[theNameDefinedinThisFile](), example, setTodoGateway().
Next we have the first bean definition. It is for our gateway object. As you can see, we have named it "todoGateway" and have set its class to "model.todos.todoGateway", which is the path to the todoGateway.cfc. Inside of our bean definition, we have a <property> element named "dsn" with a value of "todos". This value will be injected into our todoGateway using a setter method called setDSN(). Let's go add that method.
In our todoGateway.cfc add this method:
<cffunction name="setDSN" access="public" returntype="void" output="false">
<cfargument name="dsn" type="string" required="true" hint="Injected" />
<cfset variables.dsn = arguments.dsn />
</cffunction>
There, that was easy. Now, when ColdSpring prepares the todoGateway object, it will call the setDSN() method and pass in "todos" as the dsn argument. Remember that when we set up todoGateway that all of our datasource attributes in our <cfquery> tags were set to #variables.dsn#.
NOTE: This is probably a good place to stop and explain that even though we are using the term "bean" to describe these objects. They are not "beans" in the same sense as out List.cfc and Item.cfc beans. In the world of Java, just about everything is a bean (I guess), and ColdSpring is based on Spring (for Java). When they created ColdSpring, they decided to keep the elements the same.
Next, in our coldspring.xml.cfm file we have a bean definition for our todoService. Its class points to the todoService.cfc file at model.todos.todoService, and it too, has a property. But its property is a little different. This time, ColdSpring will be looking for the setter method called setTodoGateway() and it will be passing in the entire gateway object. So in our todoService.cfc we need to add the appropriate method.
<cffunction name="setTodoGateway" access="public" returntype="void" output="false">
<cfargument name="todoGateway" required="true" type="model.todos.todoGateway" />
<cfset variables.todoGateway = arguments.todoGateway />
</cffunction>
This method receives the gateway object as an argument and sets it into the variables scope of the service object. The service object will now have access to the todoGateway.
The final step in setting up ColdSpring for our application is to tell ColdBox which dependency injection framework we are using, and where to look for the config file.
In our coldbox.xml.cfm file look for these lines (around line 80):
<!--IOC Framework if Used, else leave blank-->
<Setting name="IOCFramework" value="" />
<!--IOC Definition File Path, relative or absolute -->
<Setting name="IOCDefinitionFile" value="" />
We are going to change them to read:
<!--IOC Framework if Used, else leave blank-->
<Setting name="IOCFramework" value="ColdSpring" />
<!--IOC Definition File Path, relative or absolute -->
<Setting name="IOCDefinitionFile" value="config/coldspring.xml.cfm" />
Now we have set up our application to use ColdSpring to manage our dependency. Whenever we use ColdSpring to create the todoService object, it will also, automatically, create the todoGateway (including passing in the DSN name) and then inject the todoGateway into the todoService.
Reinitializing the Framework
After making changes to the ColdBox config, the ColdSpring config, or any of our service objects, we need to reinitialize the framework. This can be done in at least 3 ways in ColdBox. If you are in debug mode, then at the bottom of your screen you should see a "Reinitialize Framework" button:
If you are running at least ColdBox 2.6.1, you will have the ColdBox slide bar on the left hand side of your screen. You can use the "Reload Framework" link in there.

The last option for reinitializing the framework is to add "&fwreinit" to the end of the URL string.
http://todolists:81/index.cfm?event=list.addList&fwreinit
Instantiating our User Service
Now we can instantiate our userService in our doAddList() handler method so that we can actually use it to get some stuff done.<cffunction name="doAddList" access="public" returntype="void" output="false">
<cfargument name="Event" required="true" type="coldbox.system.beans.requestcontext" />
<cfset var todoService = getPlugin("ioc").getBean("todoService") />
</cffunction>
This very simple method receives the Event object as an argument, then, using the IOC plugin from ColdBox it retrieves a fully constructed todoService object from ColdSpring.





Doug
You have an error in the final code example. You want to get the 'todoService' bean, not the 'userService' bean.
A general question, and maybe it's because I'm new to OO. At the start of the post you say you don't want to create an instance of model.todos.todoGateway in an init() method of the todoService because it feels like hard-coding. But in the setTodoGateway method created later you specify that the argument must be of type model.todos.todoGateway. In both cases you had to explicitly name the class. You could not pass in an object of another type to setTodoGateway() without changing the cfargument tag, correct?
First, thanks for pointing out my typo. I have been working with a userService so much lately, I had to change that about 5 times in my post. I guess I missed one. I will fix it.
Second, great observation. You are absolutely right. By hard coding the type I am only allowing a certain kind of gateway object to be passed in and I find myself in the same situation as I was by hard-coding it in the init() method.
That said, I could change it to type="any" and allow any kind of object. I could also create a gateway interface and force any gateway that is passed in to agree to the contract enforced by that interface. I only recently learned of this option, and I think it is pretty cool, not sure if I would use it though.
The main reason I did it the way I did was to show how dependency injection is done. Like I said, this is a simple enough example that DI is probably not needed. But in a more complex example, where there are considerably more dependencies or even circular dependencies, the init() method way of doing it would become ugly an cumbersome.
But you are right. Thanks :)
DI = Dependency Injection
IoC = Inversion of Control
In the coldbox.xml.cfm, I have enabled the IOC integration as below:
<IOC>
<Framework type="coldspring" reload="true" objectCaching="false">config/coldspring.xml.cfm</Framework>
<!--<ParentFactory type="coldspring or lightwire>definition file</ParentFactory> -->
</IOC>
Thanks for the comment. There might be a few things wrong. The first thing that comes to mind is that the todoService.cfc file is not located in /model/todos/todoService.cfc os that something is spelled wrong. Are you on a case sensitive system? Or are you running the todo application in a sub directory that might be changing the path to something like /todoLists/model/todos/todoService.cfc?
You might need to set up a mapping to /model.
http://localhost/projects/coldboxtraining/todolist..., my bean declaration should be pointing to this lengthy path
projects.coldboxtraining.todolist.web.model.todos.todoService :-)
Now my coldspring.xml.cfm looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dt...;
<beans default-autowire="byName">
<bean id="todoGateway" class="projects.coldboxtraining.todolist.web.model.todos.todoGateway">
<property name="dsn"><value>todos</value></property>
</bean>
<bean id="todoService" class="projects.coldboxtraining.todolist.web.model.todos.todoService">
<property name="todoGateway">
<ref bean="todoGateway" />
</property>
</bean>
</beans>
Thanks again for the excellent guide. I can now continue the rest of the tutorial series.