Learning Flex Part 5: Using ColdSpring AOP to Create Arrays of Value Objects
One of the great things I discovered while working on the Flex version of my Bookstore sample application was another affirmation of the benefits of a good service layer. In this case, it was very easy to take query data coming back from my service layer and convert it into an array of Value Objects that Flex will then translate into ActionScript Value Objects.
By using ColdSpring's AOP capabilities, I was able to create this translation without having to modify my existing service layer objects at all. Further, I was able to do this in a fairly generic way. This technique has its limitations, but I think it will work fine in most situations.
Say I have an existing service layer method that returns a query, like this:
<cffunction name="getFeaturedProducts" access="public" returntype="query" output="false" hint="I return the products marked as 'featured products'.">
<cfreturn getProductGateway().getByFields(featuredProduct=1) />
</cffunction>
As you can see, this returns a query of featured products, meaning the products I might want featured on the home page because they are on sale or something. In the normal HTML version of the Bookstore, this query would then be rendered into a table in one of the View templates.
In the Flex UI, I want these to be converted into an array of Product Value Objects so that they can be used in a DataGrid or a TileList. To do this, I created a generic AOP Advice called VOConverterAdvice, which looks like this:
<cfcomponent output="false" displayname="VOConverterAdvice" hint="" extends="coldspring.aop.MethodInterceptor">
<cffunction name="init" returntype="any" output="false" access="public" hint="Constructor">
<cfreturn this />
</cffunction>
<!--- PUBLIC METHODS --->
<cffunction name="invokeMethod" returntype="any" access="public" output="false" hint="">
<cfargument name="methodInvocation" type="coldspring.aop.MethodInvocation" required="false" hint="" />
<cfset var local = StructNew() />
<cfset local.voArray = ArrayNew(1) />
<cfinvoke
component="#arguments.methodInvocation.getTarget()#"
method="#arguments.methodInvocation.getMethod().getMethodName()#"
argumentcollection="#arguments.methodInvocation.getArguments()#"
returnvariable="local.result" />
<cfset local.rowCounter = 1 />
<cfloop query="local.result">
<cfset local.tempData = StructNew() />
<cfloop list="#local.result.columnList#" index="local.thisColumn">
<cfset local.tempData[local.thisColumn] = local.result[local.thisColumn][local.rowCounter] />
</cfloop>
<cfset ArrayAppend(local.voArray, CreateObject('component', getVOType()).init(argumentCollection=local.tempData)) />
<cfset local.rowCounter++ />
</cfloop>
<cfreturn local.voArray />
</cffunction>
<cffunction name="setVOType" access="public">
<cfargument name="voType" />
<cfset variables.voType = arguments.voType />
</cffunction>
<cffunction name="getVOType">
<cfreturn variables.voType />
</cffunction>
</cfcomponent>
What this does is call the underlying service layer method to get the query. Then, for each row in the query, it populates a simple Value Object CFC based on the query column names. In this way, it builds up an array of Value Objects, and then returns that array. The Value Object CFC might look like this:
<cfcomponent displayname="ProductVO" output="false">
<cfproperty name="productID" type="numeric" />
<cfproperty name="productName" type="string" />
<cfproperty name="productDescription" type="string" />
<cfproperty name="productPrice" type="numeric" />
<cfproperty name="asin" type="string" />
<cfproperty name="featuredProduct" type="numeric" />
<cffunction name="init" access="public" returntype="presentation.common.components.ProductVO" output="false">
<cfargument name="productID" type="numeric" required="false" />
<cfargument name="productName" type="string" required="false" />
<cfargument name="productDescription" type="string" required="false" />
<cfargument name="productPrice" type="numeric" required="false" />
<cfargument name="asin" type="string" required="false" />
<cfargument name="featuredProduct" type="numeric" required="false" />
<cfset this['productID'] = arguments.productID />
<cfset this['productName'] = arguments.productName />
<cfset this['productDescription'] = arguments.productDescription />
<cfset this['productPrice'] = arguments.productPrice />
<cfset this['asin'] = arguments.asin />
<cfset this['featuredProduct'] = arguments.featuredProduct />
<cfreturn this />
</cffunction>
</cfcomponent>
The ColdSpring XML to create this Remote Proxy and apply this VOConverterAdvice looks like this:
<bean id="remoteProductService" class="coldspring.aop.framework.RemoteFactoryBean" lazy-init="false">
<property name="interceptorNames">
<list>
<value>ProductVOConverterAdvisor</value>
</list>
</property>
<property name="target">
<ref bean="productService" />
</property>
<property name="serviceName">
<value>RemoteProductService</value>
</property>
<property name="relativePath">
<value>/presentation/flexbookstore/remote/</value>
</property>
<property name="remoteMethodNames">
<value>*</value>
</property>
<property name="beanFactoryName">
<value>BeanFactory</value>
</property>
</bean>
<bean id="ProductVOConverterAdvisor" class="coldspring.aop.support.NamedMethodPointcutAdvisor">
<property name="advice">
<ref bean="ProductVOConverterAdvice" />
</property>
<property name="mappedNames">
<value>*</value>
</property>
</bean>
<bean id="ProductVOConverterAdvice" class="presentation.common.components.VOConverterAdvice">
<property name="VOType">
<value>ProductVO</value>
</property>
</bean>
So Flex calls getFeaturedProducts() on the RemoteProductService. ColdSpring automatically generates this Remote Proxy for me. It then applies the VOConverterAdvice, which gets the query results from the underlying ProductService, converts that query into an array of ProductVO CFCs, and returns that array to Flex. Flex then automatically converts that into an array of ProductVO ActionScript classes, which I can then use however I want to in my Flex application.
It's a bit verbose, but it only has to be done once and now all of the methods in my ProductService that return product queries can now return arrays of Product Value Objects to Flex. And again, the big win here is that I'm able to do this without having to change anything in my existing ProductService.
I do wish there was a way that I could have the Remote Proxy tell the VOConverterAdvice which kind of Value Object I want to translate instead of having to explicitly define it, but there isn't (that I am aware of). This kind of goes back to my old idea of being able to pass additional metadata to Advices, but that's another discussion. Since you can supply ColdSpring with your own XML or XML Document Object instead of an XML file name, one workaround might be to build up the XML dynamically, but I haven't tried it yet. But this is a digression. However it is done, the great thing about all this is that my existing service layer is able to feed data to my Flex application with no changes!
That about sums it up for my approach to leveraging ColdSpring to help me feed data to my Flex applications. If anyone has any comments or advice (no pun intended) on this, or has a different way to approaching the problem, I'd love to hear about it in the comments.




My only question with your implementation is that you use CFINVOKE to run the original method. Why do you do this instead of just running the proceed() method and collecting the return data that way?
On a semi-unrelated note I just wanted to confirm something about AOP.. I had it in my head that I would be able to access the local data of the method that is intercepted.. I am assuming that this is not possible as the method is executed in it's own space?
The problem I am having is that I really need the data that is processed within the method (I need to log some data that the method uses to do it's job). I can see now that I will have to refactor my service methods to ensure that they return the data that my advice needs.. Is this often the case with AOP? I.E your service layer design has to be done in a certain way to allow you to access the data you need?
I am starting to feel that it may just be worth injecting my notification service into the service that needs it and putting the logging method call in the functions that need it (there aren't that many), although I am still feeling the pull to keep on with the AOP approach as I do see the value in abstracting things such as logging away from the main services..
Thanks in advance for any help you can give me..
You are correct that you cannot access local variables of the target method. They are private to everything, even the Advice. Remember, to the target method, the Advice is "just another caller". Nothing special about it.
If you need data that the target method uses, I would not refactor the target method to return something specific to the Advice. Clearly you must be passing something into the method as arguments, or getting something back from the method. The target method shouldn't be "knowing" or "getting at" any data that the Advice itself also could not get to. So you shouldn't have to refactor the target method, but rather design the Advice so that it can properly get at the same kind of data. Make sense?