Showing posts with label cfml gateways. Show all posts
Showing posts with label cfml gateways. Show all posts

Wednesday, October 15, 2008

Generic ColdSpring Gateway Service Code

I've been digging into ColdFusion's Gateway's of late and created a simple generic Gateway that works great with coldspring (GatewayService.cfc). The beauty is the simplicity. With this process, you can send any 'message' to the gateway and have the application run it asynchronously. The message you send will be any unit of work in your coldspring model. For example if you have an XmlManager object that has a method of 'generateRSSFeeds', you can pass it a Structure of data as an argumentcollection, as well as the object name and method name and have coldspring execute that for you w/in your Gateway.


<cffunction name="onIncomingMessage" output="false" access="public" returntype="void" hint="Generic Method that will execute any method on the Service Factory - Struct: beanname (coldspring Bean to call), methodname (method to call on cs bean), argumentcollection (data passed as argument collection to the method)">
<cfargument name="CFEvent" type="struct" required="yes">

<cfset var data = arguments.CFEvent.data/>
<cfset var errorMessage = ''/>

<cfif not structkeyexists(data,"args")>
<cfset data.args = StructNew()>
</cfif>
<!---
Structure:

beanname
methodname
args: argumentcollection

--->
<cftry>
<cfinvoke component="#application.serviceFactory.getBean(data.beanname)#"
method="#data.methodname#"
argumentcollection="#data.args#"/>
<cfcatch type="any">
<cfset errorMessage = cfcatch.Message/>
</cfcatch>
</cftry>
<cfif structkeyexists(data.args,"debug")>
<cflog application="true" file="cfmlgateway" text="CF Gateway Called: bean: #data.beanname# method: #data.methodname# data: #structKeylist(data.args)# #errorMessage#">
</cfif>

<cfreturn />
</cffunction>



There is also a 'server.init.cfm' feature that sets up this gateway through the ColdFusion Admin API and makes sure that it exists in the server that's running your application - and any changes to the gateway it will determine and recreate it when needed. The gateway is a CFML gateway and is given a name that uniquely identifies it (just like a DSN). Note: You must call the login method of the cfide.adminapi.administrator before you are allowed access to do this.


<cfset Local.gatewaypath = expandpath('.') & '\model\gateway\GatewayService.cfc'/>
<cfset Local.eventGateway = createObject( "component", "cfide.adminapi.eventgateway")/>
<cfset Local.gatewayFound = false/>

<!--- query the gateway Instances to find a match and if not create it --->
<cfset Local.tmpArray = Local.eventGateway.getGatewayInstances()/>

<cfloop from="1" to="#arraylen(Local.tmpArray)#" index="Local.i">
<cfif Local.tmpArray[Local.i].gatewayid eq 'mygatewayname'>
<cfset Local.gatewayFound = true/>

<!--- check and see if the cfcpath is the same- if not delete it and recreate it --->
<cfif Local.tmpArray[Local.i].cfcpaths[1] neq Local.gatewaypath>
<cfset Local.eventGateway.deleteGatewayInstance(Local.tmpArray[Local.i].gatewayid)>
<cfset Local.gatewayFound = false/>
</cfif>
<cfbreak/>
</cfif>
</cfloop>


<cfif not Local.gatewayFound>

<!--- Create gateway instance --->
<cfset Local.tmpArray = ArrayNew(1) />
<cfset Local.tmpArray[1] = Local.gatewaypath/>

<cfset Local.eventGateway.setGatewayInstance('mygatewayname','CFML',Local.tmpArray,'','auto') />
<cfset Local.eventGateway.startGatewayService() />

<cfset Local.eventGateway.startGatewayInstance('mygatewayname') />
<cflog log="Application" text="Gateway mygatewayname Created" type="information"/>
</cfif>


There is also a 'gatewaymanager.cfc' that simply is the API to all your gateway service calls, in which you can add on other things like debugging and error handling etc if needed.


<cffunction name="sendMessage" output="false" access="public" returntype="boolean" hint="Returns true of false if the message was successfully sent to the gateway">
<cfargument name="beanname" type="string" required="true"/>
<cfargument name="methodname" type="string" required="true"/>
<cfargument name="args" type="struct" required="false" default="#StructNew()#"/>

<cfset var boolReturn = SendGatewayMessage('mygatewayname',arguments)>

<cfreturn boolReturn />
</cffunction>


With great power come great responsibility - please use wisely!

Object Refactoring into Structures for Performance

Lately I've been tasked with refactoring some of our batch processes that are taking entirely too long to run - especially with large datasets (10-25K records). I've noticed one thing in common with each procedure that I've had to rewrite - they have heavy reliance on objects to do the work for them i.e. I'm doing some work on a 'job' object, for each job i'll call 'getJobById' which returns a populated job bean from the db in which I manipulate it and then call the 'save' method on it - and repeat 10-25 thousand times. With cf8.01 we're seeing heap size of the JVM being used up after a period of time crunching through these methods. They become slow to the point of unresponsive and ultimately bring the jvm down or cause the heap size to become too large and the process halted.

Here are some of the highlights that I've done in refactoring these processes:
1.) Only query for the data I need (i.e. NOT selecting all records when all i need are the id field and a location field)
2.) Verity that all local variables are var scoped.
3.) remove any calls to 'getJobById' in favor of a simple reusable JobStructure.
4.) populate the job Structure from the query directly and then pass this simplified structure into an alternate method that accepts the structure and does any massaging it needs to with the data (returning it by reference).
5.) Create a specific 'update' method in my DAO that accepts this Structure and saves ONLY the data that is needed vs. the entire bean object.


The Results??? Extremely fast execution and stable memory throughout the entire process - regardless of how long the entire process may take. We went from roofing the JVM to maintaining a stable memory footprint on these executions, while also staying true to good programming techniques - and even 'introspecting' a single Object before the run and exposing it's internal 'metadata' to the job Structure as a base for the operation - and then proceeding from there - Appending data and overwriting the Structure each time through the loop.
i.e. StructAppend(jobStruct,newStruct,'true')

CreateObject('component','job').init().getInstance()
We have all our instance data in a 'variables.instance' structure w/in the object. We also create a 'getInstance' method which returns that structure. That means we can use this structure of the objects data instead of using a new object each time - NOR using the bean each time. We're still able to change/modify the Bean metadata and still have that available to the other calling methods that require it.

I.e. We can call the 'getBeanConfig()' method on the job object which returns a structure of metadata (structure of structures) about things like field size, and nullability etc. of each field in our bean.


<cffunction name="getBeanconfig" output="false" access="public" returntype="Struct" hint="returns the entire bean config structure">
<cfreturn variables.beanconfig />
</cffunction>

<cffunction name="setBeanConfig" access="private" returntype="void" output="false" displayname="category config" hint="I setup the configuration for the bean category.">
<cfscript>
variables.beanconfig = structnew();
variables.beanconfig.title = StructNew();
variables.beanconfig.title.isnullable=0;
variables.beanconfig.title.maxlength = 95;
</cfscript>
</cffunction>


When working with thousands of beans in a process - originally it was a lot easier to get the thing working by just calling 'getBeanById()' and then manipulating it and calling 'Save()' on it. Over time I've learned that it's not sustainable and may need to be refactored. As long as you're being careful to var scope all your local variables in your cfcs and use a lowest common denominator approach to large long running batch requests, you will see a large performance gain by extracting your object structure data and working with a single copy of it vs. populating your entire bean each time through a loop and saving the entire bean once you're done with it. That plus leveraging the CFML Gateway to thread your executions, you'll be off the ground flying once again!