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:

  1. Create our todoService.cfc
  2. Set up ColdSpring to create our todoService object and inject its dependencies
  3. 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.

  1. 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
  2. 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.

Conclusion

Today was a good day. We were able to set up ColdSpring to work with our application. We modified our todoGateway and todoService to work with ColdSpring's dependency injection and we modified our event handler to instantiate the todoService using the IoC plugin to request the object from ColdSpring. Next time we will be able to add methods to our service layer to create a List bean, populate it, and persist it using the todoGateway.

Comments
Doug's Gravatar Jason, this is great stuff ... and two articles in two days. Now I can kill off another day of work to "study" this.
Doug
# Posted By Doug | 11/4/08 9:18 AM
Jason Dean's Gravatar @Doug, I am glad I could help you be less productive :) Thanks for the comment. I sat down last night to start a post for later release and I just got wrapped up in writing it and ended up finishing. I wish I had time to do more of them, but they take so long.
# Posted By Jason Dean | 11/4/08 10:28 AM
Mike Mongeau's Gravatar Nice work again Jason. I'm enjoying this series a lot.

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?
# Posted By Mike Mongeau | 11/4/08 2:53 PM
Jason Dean's Gravatar @Mike,

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 :)
# Posted By Jason Dean | 11/4/08 3:23 PM
Luis Majano's Gravatar The next step is enabling autowire for your handlers and getting rid of those getPlugin() calls.
# Posted By Luis Majano | 11/4/08 5:06 PM
Jason Dean's Gravatar @Luis. Yes! I just learned about auto-wiring a couple of weeks ago, and I love it! I will demonstrate in a coming post.
# Posted By Jason Dean | 11/4/08 6:20 PM
Jim_Collins's Gravatar I've reached the conclusion that ColdBox requires the use of either Lightwire or Coldspring. I have seen no examples that use DAOs and gateways that don't use one or the other.
# Posted By Jim_Collins | 5/2/09 8:19 PM
Jason Dean's Gravatar @jim, ColdBox does not require the use of a DI/IoC framework (like ColdSpring or Lightwire), but using one can make development MUCH simpler, which is likely the reason that most of us use it for examples. I could develop an application without DI, but I don;t want to. When you get into using DI, you will probably agree.

DI = Dependency Injection
IoC = Inversion of Control
# Posted By Jason Dean | 5/2/09 8:42 PM
Michael T's Gravatar Thanks for the great tutorials Jason! I am new to the CB framework, I've been following your tutorials with CB 3 beta 3 and it seems to run all fine until I hit an error at this tutorial. When I attempt to call the url ... index.cfm?event=list.doAddList&fwreinit=1, I get an AutowireException... It says "Could not find the Component or Interface model.todos.todoService. Any idea what could have been incorrect?
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>
# Posted By Michael T | 11/15/09 8:54 AM
Jason Dean's Gravatar @michael,

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.
# Posted By Jason Dean | 11/15/09 2:50 PM
Michael T's Gravatar You're correct! The problem is in the class path of the bean declared in my coldspring.xml.cfm. In my system, I have a virtual directory mapping and CF mapping for /projects to C:\Users\Michael\Projects\ and because I am running the application under this url:

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.
# Posted By Michael T | 11/15/09 6:46 PM
BlogCFC was created by Raymond Camden. This blog is running version 5.9.1. Contact Blog Owner