I’ve been meaning to share this for many reasons, but I’ve been bogged down with projects lately. However, it has allowed the code to mature and get streamlined a bit, so it’s good that some time has passed.
This is a pattern that we’ve come up with at work to handle remote calls to ColdFusion. It is one part of our SOA initiative that allows ColdFusion and Flex developers to make service calls to our core business components using a common approach.
I’ll start at the high level and work my way down. The reason I started this project is because early on I started to notice that there were many common entities used throughout the gamut of application used by my business unit – Facilities, Teammates, Drugs, Treatments, Patients, Doctors, etc…
Each application developer had written their own code to handle retrieving information on each of these entities. I’m sure you can see where the problem is in that, so I won’t belabor the point. To stop the fragmentation, I began diagramming how to create a common service application that could be accessed by any client in the company using a simple interface.
The Remote Call Service – RemotingService.cfc
Let’s start off with the first part of the puzzle: the simple interface for calling ColdFusion Component methods in our service application.
<cfcomponent output="false">
<cffunction name="invokeService" access="remote" returntype="Any">
<cfargument name="eventName" type="string" required="true" />
<cfargument name="eventData" type="struct" required="false" default="#structNew()#" />
<cfreturn application.beanFactory.getBean("ServiceRunner").run(argumentCollection=arguments) />
</cffunction>
</cfcomponent>
Can’t get more simple than this. Using any remoting technology supporting SOAP, any client can invoke this component’s invokeService() method. This is the one entry point into our service application. As I’m sure you noticed by the application.beanFactory code, we’re using ColdSpring to manage all of our dependencies, AOP features, and event maps (more on this later).
The Service Response Value Object – ResponseVO.cfc
The next concept we tackled was not returning complex objects; we wanted a simple and logical structure to what gets returned to the client. Also we didn’t want the response object sitting around in memory sucking up RAM on the machine. We wanted this object to be created and destroyed on each remote request (shows below in the ServiceRunner code).
What got created was a simple Value Object that defines the keys of the response: name of the event, a success boolean, the data object, and an error structure (which is only populated if an error occurs).
<cfcomponent output="false"
hint="Value object that is returned for every response">
<cfproperty name="eventName" type="string" />
<cfproperty name="success" type="boolean" />
<cfproperty name="data" type="any" />
<cfproperty name="error" type="Struct"/>
<cfset this.eventName = "" />
<cfset this.success = true />
<cfset this.data = "" />
<cfset this.error = structNew() />
</cfcomponent>
The Service Runner – ServiceRunner.cfc
Once we’d defined how services would be called and what the response would be, we then had to write the mechanism to actually execute the method and populate the response VO. The service runner is defined as a bean via ColdSpring (shown below) and is instantiated from the RemotingService component (shown above).
The RemotingService then calls the public run() method which creates a VO, executes the requested event using the private runEvent() method, and then returns the populated VO.
<cfcomponent output="false">
<cffunction name="run" access="public" returntype="Any" output="false" description="Responds to remote request">
<cfargument name="eventName" type="string" required="true" />
<cfargument name="eventData" type="Struct" required="false" default="#structNew()#" />
<cfset var local = StructNew() />
<cfset var vo = createObject("component", "utility.remoting.ResponseVO") />
<cfset vo.success = true />
<cfset vo.eventName = arguments.eventName />
<cftry>
<cfset local.eventData = arguments.eventData />
<cfif application.beanFactory.containsBean("event.#arguments.eventName#")>
<cfset vo.data = runEvent(eventName=arguments.eventName, eventData=arguments.eventData) />
<cfelse>
<cfthrow detail="Event definition not found" message="event.#arguments.eventName# was not found." />
</cfif>
<cfcatch type="any">
<cfset vo.success = false />
<cfset vo.data = "" />
<cfset vo.error = cfcatch />
</cfcatch>
</cftry>
<cfreturn vo />
</cffunction>
<cffunction name="runEvent" access="private" returntype="any" output="false" hint="Calls the event defined in the EventMaps definition file">
<cfargument name="eventName" type="string" required="true" />
<cfargument name="eventData" type="Struct" required="true" />
<cfset var local = structNew() />
<cfset local.response = "" />
<cfset local.eventInfo = application.beanFactory.getBean("event.#arguments.eventName#") />
<cfset local.eventHandler = application.beanFactory.getBean(local.eventInfo.getBeanName()) />
<cfinvoke component="#local.eventHandler#" method="#local.eventInfo.getMethodName()#" returnvariable="local.response" argumentcollection="#arguments.eventData#" />
<cfreturn local.response />
</cffunction>
</cfcomponent>
Defining Events as Beans – EventMaps.xml
To define our events, we create a bean for each one using the RemoteEventMap.cfc as the template for each one. The RemoteEventMap (code shown below) has two properties: beanName and methodName. These are used in the runEvent() method of the ServiceRunner (shown above) to obtain the properties needed for the CFINVOKE tag that executes the actual method we need.
<!DOCTYPE beans SYSTEM "ColdSpring.dtd"
[
<!ENTITY appName "yourAppName">
<!ENTITY appCFCMapping "&appName;.model">
<!ENTITY eventCFCMap "utility.remoting.RemoteEventMap">
]>
<beans>
<bean id="ServiceRunner" class="coldspring.aop.framework.ProxyFactoryBean">
<property name="target">
<bean class="utility.remoting.ServiceRunner" />
</property>
<property name="interceptorNames">
<list>
<value>metricsAdvisor</value>
</list>
</property>
</bean>
<bean id="event.getFacilities" class="&eventCFCMap;">
<property name="beanName">
<value>FacilityService</value>
</property>
<property name="methodName">
<value>getHeldFacilities</value>
</property>
</bean>
</beans>
The Event Bean – RemoteEventMap.cfc
This is the simple RemoteEventMap that each event creates as a bean.
<cfcomponent output="false">
<cffunction name="init" access="public" output="false" returntype="RemoteEventMap">
<cfset variables.beanName = "" />
<cfset variables.methodName = "" />
<cfreturn this />
</cffunction>
<cffunction name="getBeanName" access="public" output="false" returntype="string">
<cfreturn variables.beanName />
</cffunction>
<cffunction name="setBeanName" access="public" output="false" returntype="void">
<cfargument name="beanName" type="string" required="true" />
<cfset variables.beanName = arguments.BeanName />
</cffunction>
<cffunction name="getMethodName" access="public" output="false" returntype="string">
<cfreturn variables.methodName />
</cffunction>
<cffunction name="setMethodName" access="public" output="false" returntype="void">
<cfargument name="methodName" type="string" required="true" />
<cfset variables.methodName = arguments.methodName />
</cffunction>
</cfcomponent>
In Part II – which I will hopefully finish up tomorrow – I will show implementation examples of this pattern for ColdFusion and for Flex.