Related Topics: ColdFusion on Ulitzer, Java Developer Magazine

CFDJ: Article

ColdFusion Developer's Journal Special "Frameworks" Focus Issue: ColdSpring

A framework for ColdFusion Components – Pet Market implementation

There is nothing special going on here: the UserService just takes the supplied UserDAO and retains a reference to it (by placing it in its variable's scope). You don't have to use <property/> tags and setter methods to expose your CFCs to dependency injection: ColdSpring also supports a <constructor-arg> tag. The <contructor-arg/> tag tells ColdSpring to inject dependencies as arguments to a CFC's init() method, which is also known as a "constructor." You have to provide the <cfargument/> tags so that ColdSpring can pass in your CFC's dependencies, but otherwise it works the same as <property/> and setter methods.

The second <property/> tag within our UserService definition is a bit different. Our UserService implementation needs a SessionService in order to perform certain tasks, but so do other components within our model. We don't define the SessionService within the UserService as we did with the UserDAO, because the UserService isn't the only CFC that needs to use the SessionService. Thus, we use the <ref/> tag to supply the UserService with the session service. The <ref/> tag basically just instructs ColdSpring to use a component that has been defined elsewhere within the BeanFactory (the order of the definitions doesn't matter, e.g., you don't have to define the SessionService before the UserService in order to use the SessionService in a <ref/> tag).

We used the same techniques to define the rest of our Pet Market model within ColdSpring (look at components.xml to see it in its entirety). All of the application's components are wired together using ColdSpring, and there are some pretty complex dependencies that would have been very difficult to implement without ColdSpring's help. For instance, our "ProductService" depended on several components in order to function correctly, one of them being a "ProductItemDAO". Now, what the ProductItemDAO is or does is irrelevant to ColdSpring; it's just part of our model. What is relevant is that the ProductItemDAO depended on the ProductService, just as the ProductService depended on the ProductItemDAO. They both needed to be able to call methods on each other, and this situation is known as a "circular dependency." Here's a snippet of the XML that defines this complex situation:

<bean id="ProductService" class="petmarket.component.product.ProductService">
...
<property name="ProductItemDAO">
<ref bean="ProductItemDAO"/>
</property>
</bean>

<bean id="ProductItemDAO" class="petmarket.component.product.ProductItemDAO">
<property name="ProductService">
<ref bean="ProductService"/>
</property>
</bean>

ColdSpring actually supports circular dependency resolution when you are using setter-based injection (e.g., you are using <property/> instead of <constructor-arg/>), so the above is perfectly legal.

After the ColdSpring BeanFactory is loaded with BeanDefinitions, your controller layer code would ask for a ColdSpring "Bean" (remember, it's just a CFC) using the BeanFactory's getBean() method. You pass the ID of the Bean if you want to getBean(), meaning that if you wanted to get the SessionService defined above, you would call getBean('SessionService'). You can actually see this taking place in our Pet Market's application.cfm, after we created and configured the BeanFactory:

<cfset request.SessionService = application.beanFactory.getBean("SessionService") />

Within our Pet Market model, when we got to the OrderService, we realized that some of our business requirements were pretty complex. To save an order we need to perform multiple tasks, save the gathered user data, save the purchased items, and update the inventory. We also decided it may be a good idea to store a backup of the order in a log file, but we were not sure if this is a concrete business requirement. This is where using ColdSpring in a "service-oriented" architecture really shines. If you take a look at the OrderService, you see that it contains setters for the UserService and ProductService, as well as the OrderDao it uses to save the actual order items to the database. When placeOrder(user,cart) is called on the OrderService, saveUserForOrder(user) is called on the userService, placeOrder(cart) is called on the OrderDao, and updateProductQuantities(cartItems) is called on the ProductService. The actual transaction management is not handled in any of the DAOs, as this would make it impossible to propagate such a complex business process. Instead the transaction is managed by the OrderService component. In a larger application, you may have many complex business processes, and without using a container like ColdSpring to manage collaborators, you may end up writing components that perform multiple tasks at once, which will unfortunately make them far less reusable and coherent.

The one thing you may have noticed that was left out of this process was logging a record of the order. This brings up a pretty tricky issue, because logging the order is not necessarily part of the business logic; it's not really even a business requirement. What we would like to do is add or remove this feature declaratively, instead of adding any code to our existing services. Luckily ColdSpring's new aspect-oriented programming framework allows us to do exactly this. Before we get into exactly how this is implemented, a quick overview of aspect-oriented programming is in order. Since a full discussion of aspect-oriented programming is outside the scope of this article, we'll briefly describe the steps involved, and include links to resources at the end of the article.

Aspect-oriented programming assists in applying certain types of "cross-cutting" functionality broadly across model components. Functionality like logging is not necessarily part of the business logic of a UserService component, but we may still want to add logging to certain methods within that component. AOP also allows us to write logging code in one place and through configuration apply it to one or many of our model components. In order to use AOP to log an order, we will write an Advice component that contains the logging code, configure an instance of a ColdSpring "Pointcut Advisor" component that will contain the Advice and identify the names of methods to apply it to, and configure a ColdSpring ProxyFactoryBean component to create a new instance of a supplied target object with the now advised methods. The process of creating a new component that combines the code in the Aspects with the code in the target object is known as weaving. There are a few different methods of weaving in AOP, but we use a system of dynamic runtime compilation to create a proxy object that contains the target object and the Aspects. This proxy object will be the same type as the target object, so from the perspective of the rest of your application, it will appear to be the original. The proxy object will also contain all of the same methods as the original, but each method call will check for a match from the Pointcut, and run through any Advice configured before, after or around the method call if necessary. So let's take a look at our components.xml file to see how this works in practice. First we have the definition of the OrderServiceTarget, which is the OrderService component.


<bean id="OrderServiceTarget"
class="petmarket.component.order.OrderService">
<property name="TaxRate"><value>.08</value></property>
<property name="ShippingMethodGateway">
<bean id="ShippingMethodGateway"
class="petmarket.component.order.ShippingMethodGateway"/>
</property>
<property name="OrderDAO">
<bean id="OrderDAO"
class="petmarket.component.order.OrderDAO">
</bean>
</property>
<property name="ProductService">
<ref bean="ProductService"/>
</property>
<property name="UserService">
<ref bean="UserService"/>
</property>
</bean>

More Stories By Dave Ross

David Ross is a ColdFusion Developer.

More Stories By Chris Scott

Chris Scott is a ColdFusion Developer.

Comments (0)

Share your thoughts on this story.

Add your comment
You must be signed in to add a comment. Sign-in | Register

In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.