Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at the New York ColdFusion User Group (Mar. 2009) with:

Experimenting With ColdFusion 9's ObjectSave() And ObjectLoad() Functions

By Ben Nadel on
Tags: ColdFusion

During the last cf.Objective() conference, I heard someone mention ColdFusion 9's new ObjectSave() and ObjectLoad() methods. I had not yet heard of these methods at the time so I was not sure what they do. Apparently, they work with the serialization and deserialization of ColdFusion objects to and from byte arrays (binary values) respectively. I am told that this kind of functionality is great for caching objects or sharing them across multiple ColdFusion instances. In any case, I wanted to sit down and do a little experimenting for myself.

First, I just wanted to get a sense of how the serialization worked - did it work, was state maintained, and was the link to the original object broken? I am sure that most of these questions are obvious, but I like to start with a proven base before I get too complicated.

To test this, I created a simple ColdFusion 9 component with a few synthesized accessors:

Woman.cfc

  • <cfcomponent
  • output="false"
  • accessors="true"
  • hint="I provide lady behavior.">
  •  
  •  
  • <!--- Define properties that get accesssors. --->
  • <cfproperty name="name" type="string" />
  • <cfproperty name="age" type="numeric" />
  • <cfproperty name="hair" type="string" />
  •  
  •  
  • <cffunction
  • name="init"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I return an initialized object.">
  •  
  • <!--- Define arguments. --->
  • <!--- For this example, we'll just use named arguments. --->
  •  
  • <!--- Loop over arguments to set them. --->
  • <cfloop
  • item="local.property"
  • collection="#arguments#">
  •  
  • <!---
  • Check to see if this property has an appropriate
  • setter defined.
  • --->
  • <cfif structKeyExists( this, "set#local.property#" )>
  •  
  • <!--- Store property. --->
  • <cfinvoke
  • component="#this#"
  • method="set#local.property#">
  •  
  • <cfinvokeargument
  • name="#local.property#"
  • value="#arguments[ local.property ]#"
  • />
  •  
  • </cfinvoke>
  •  
  • </cfif>
  •  
  • </cfloop>
  •  
  • <!---
  • Return this object reference for method chaining.
  • Remember, in ColdFusion 9, the NEW operator requires
  • the use of a return value.
  • --->
  • <cfreturn this />
  • </cffunction>
  •  
  • </cfcomponent>

As you can see, this ColdFusion component has three properties - name, age, hair - that all have synthesized getters and setters. I then created a test page that would instantiate this component, set some values on it, save it, load it, and then set some more values.

  • <!--- Create an instance for Sarah. --->
  • <cfset sarah = new Woman(
  • name = "Sarah",
  • hair = "Brunette"
  • ) />
  •  
  • <!--- Serialize sarah as binary (byte array) and save to disk. --->
  • <cfset objectSave(
  • sarah,
  • expandPath( "./sarah.data" )
  • ) />
  •  
  • <!--- Change the value of the live sarah instance. --->
  • <cfset sarah.setAge( 35 ) />
  •  
  • <!--- Now, read the serialized sarah back in. --->
  • <cfset sarahFromFile = objectLoad(
  • expandPath( "./sarah.data" )
  • ) />
  •  
  • <!--- Change the hair color of the deserialized sarah. --->
  • <cfset sarahFromFile.setHair( "Blonde" ) />
  •  
  •  
  • <!--- Output our existing sarah. --->
  • <cfdump
  • var="#sarah#"
  • label="Original Sarah"
  • />
  •  
  • <br />
  •  
  • <!--- Output our ressurected sarah. --->
  •  
  • <cfdump
  • var="#sarahFromFile#"
  • label="Binary Sarah"
  • />

As you can see, after I have deserialized the sarah instance, I continue to augment it. I am doing this to see if the given property will be changed on both instance of Women? Or, if it will only be changed on the newly deserialized version.

When I run the above code, I get the following CFDump output:

 
 
 
 
 
 
ObjectSave() And ObjectLoad() Create Two Different Instances Of A ColdFusion Component. 
 
 
 

As you can see, the deserialized component was able to maintain the state (name = "Sarah"); however, once the object was serialized and then deserialized, I was working with two completely independent instances. Changing the hair color of one did not affect the hair color of the other.

So, objectSave() and objectLoad() allow us to persist a component's state; but, that makes absolutely no assumptions about the bond between objects pre and post serialization. For all intents and purposes, the object load/save life cycle appears to be akin very much like calling duplicate() on the original component.

NOTE: I have not yet tested how references to external objects work (ie. what happens when a CFC has reference to another CFC). But, like a deep copy, I assume that it will serialize all references in order to maintain object integrity.

Now that we see how objectSave() and objectLoad() work, I started to think about good use cases for them. I still live in a single-machine universe; so, the idea of passing around serialized components is not something I can speak to. Complex caching is also something I am just getting in to. However, as someone who does a good amount of research and development (R&D), one exciting use case for this type of serialization is the idea of being able to persist test data without any kind of database.

 
 
 
 
 
 
 
 
 
 

In an object-oriented world, databases are supposed to be an after-thought; you think about your domain model first and then you worry about persisting it second. When it comes to the world of testing or even of micro-deployment, this concept is even more relevant. In fact, if you had a good amount of RAM that would never get cycled, a small application or demo application might never even need to be persisted to disk.

Well, what if, rather than persisting data to disk, we simply persisted objects to disk? For complex applications, this would probably not be possible due to the pass-by-reference nature of shared objects. But, for very simple, R&D style projects, this would make life hella easy.

To explore this concept, let's look at this form that maintains a list of women:

  • <!--- Param our input variables. --->
  • <cfparam name="url.remove" type="string" default="" />
  • <cfparam name="form.add" type="string" default="" />
  •  
  •  
  • <!--- ----------------------------------------------------- --->
  • <!--- ----------------------------------------------------- --->
  •  
  •  
  • <!---
  • Create our blackbook instance. This is going to be a serializable
  • instance in which the given methods trigger state peristance.
  • --->
  • <cfset blackbook = new Serializable(
  • new BlackBook(),
  • "addWoman, removeWoman"
  • ) />
  •  
  •  
  • <!--- Check to see if we are adding a woman. --->
  • <cfif len( form.add )>
  •  
  • <cfset blackbook.addWoman( form.add ) />
  •  
  • </cfif>
  •  
  •  
  • <!--- Check to see if we are removing a woman. --->
  • <cfif len( url.remove )>
  •  
  • <cfset blackbook.removeWoman( url.remove ) />
  •  
  • </cfif>
  •  
  •  
  • <!--- ----------------------------------------------------- --->
  • <!--- ----------------------------------------------------- --->
  •  
  •  
  • <cfoutput>
  •  
  • <!DOCTYPE html>
  • <html>
  • <head>
  • <title>Serializable ColdFusion Component</title>
  • </head>
  • <body>
  •  
  • <h1>
  • My Little BlackBook
  • </h1>
  •  
  • <!--- Output the current list of women. --->
  • <ul>
  • <cfloop
  • index="woman"
  • array="#blackbook.getWomen()#">
  •  
  • <li>
  • #woman#
  • ( <a href="#cgi.script_name#?remove=#woman#">remove</a> )
  • </li>
  •  
  • </cfloop>
  • </ul>
  •  
  • <form
  • name="addForm"
  • method="post"
  • action="#cgi.script_name#">
  •  
  • <p>
  • Name:
  • <input type="text" name="add" value="" />
  • <input type="submit" value="Add Woman" />
  • </p>
  •  
  • </form>
  •  
  • <!--- Focus the form field. --->
  • <script type="text/javascript">
  • document.forms[ "addForm" ].elements[ "add" ].focus();
  • </script>
  •  
  • </body>
  • </html>
  •  
  • </cfoutput>

This page lists women and allows the user to both add and remove women to and from the list. With each action, the page is posting back to itself in order to carry out the desired command. Notice, however, that with each page load, we are re-instantiating BlackBook.cfc - the ColdFusion component that maintains the list of women.

While it's true that we are re-instantiating the BlackBook.cfc instance with each page request, we are also passing it to another object, Serializable.cfc. But, before we get into what this Serializable.cfc does, let's take a look at the BlackBook.cfc domain object:

BlackBook.cfc

  • <cfcomponent
  • output="false"
  • hint="I maintain a collection of women.">
  •  
  •  
  • <cffunction
  • name="init"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I return an initialized component.">
  •  
  • <!--- Define the class properties. --->
  • <cfset variables.women = [] />
  •  
  • <!--- Return this object reference. --->
  • <cfreturn this />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="addWoman"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I add the given woman to the collection.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="woman"
  • type="string"
  • required="true"
  • hint="I am the woman being added."
  • />
  •  
  • <!--- Add the woman. --->
  • <cfset arrayAppend( variables.women, arguments.woman ) />
  •  
  • <!--- Return this object for method chaining. --->
  • <cfreturn this />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="getWomen"
  • access="public"
  • returntype="array"
  • output="false"
  • hint="I return the collection of women.">
  •  
  • <!--- Return the array - this will pass by copy. --->
  • <cfreturn variables.women />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="removeWoman"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I remove the given woman from the collection.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="woman"
  • type="string"
  • required="true"
  • hint="I am the woman being removed."
  • />
  •  
  • <!---
  • Since this woman might be in the collection more than
  • once, keep looping until we can no longer find it.
  • --->
  • <cfloop condition="arrayDelete( variables.women, arguments.woman )">
  • <!--- Nothing to do here. --->
  • </cfloop>
  •  
  • <!--- Return this object for method chaining. --->
  • <cfreturn this />
  • </cffunction>
  •  
  • </cfcomponent>

As you can see, BlackBook.cfc is rather simple - it merely composes a ColdFusion array and provides functions for adding to and removing from this array. Notice, however, that this ColdFusion component does not have any logic pertaining data persistance. If we were to simply instantiate this object on every page request, the internal women array would continually be reset.

The maintenance of state that you saw in the above video is created by the Serializable.cfc. Our blackbook variable is not an instance of BlackBook.cfc; rather, it is an instance of Serializable.cfc that composes an instance of BlackBook.cfc. It is the Serializable.cfc that knows how to save and load the BlackBook.cfc data.

When we create our Serializable.cfc instance, we aren't just passing it a component to wrap. We are also passing it a list of "trigger methods". These are the methods whose invocation should trigger state persistance. In our case, we are indicating that "addWoman" and "removeWoman" should both trigger state persistance. That means that whenever those methods are invoked on the serializable wrapper, the wrapper should save the object state to disk for future use.

Ok, let's take a look at how Serializable.cfc works:

Serializable.cfc

  • <cfcomponent
  • output="false"
  • hint="I wrap a CFC and make it serializable, saving it whenever necessary based on method names.">
  •  
  •  
  • <cffunction
  • name="init"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="Return a serializable component.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="target"
  • type="any"
  • required="true"
  • hint="I am the CFC being wrapped for serializabillity."
  • />
  •  
  • <cfargument
  • name="triggerMethods"
  • type="string"
  • required="true"
  • hint="I am a list of method that will trigger the serialization of the target component (in order to maintain state)."
  • />
  •  
  • <!---
  • Store the path of the serialized data for this component.
  • This path will simply be the location of the component
  • plus the .serialized file extension appended to it.
  • --->
  • <cfset variables.dataFile = (
  • getMetaData( arguments.target ).path &
  • ".serialized"
  • ) />
  •  
  • <!--- Store the trigger methods. --->
  • <cfset variables.triggerMethods = arguments.triggerMethods />
  •  
  • <!---
  • Check to see if that data file currently exists. If so,
  • that's what we want to use as our target.
  • --->
  • <cfif fileExists( variables.dataFile )>
  •  
  • <!---
  • Deserialize the data and use that as our target
  • component. This will bring back the state of the
  • previous CFC.
  • --->
  • <cfset variables.target = objectLoad( variables.dataFile ) />
  •  
  • <cfelse>
  •  
  • <!---
  • The serialized version doesn't exist so just use the
  • one that was passed-in.
  • --->
  • <cfset variables.target = arguments.target />
  •  
  • </cfif>
  •  
  • <!---
  • Store a public property on the target. We need to do this
  • to help figure out when a proxy method is returning a
  • value or the target component (in which case, we want to
  • return our wrapper).
  • --->
  • <cfset variables.targetFlag = "_serializable_#createUUID()#" />
  • <cfset variables.target[ variables.targetFlag ] = true />
  •  
  • <!---
  • Create a proxy method for each public method on the
  • target component.
  •  
  • NOTE: We are looping over the target that was PASSED-IN
  • to the init() method - we are not looping over the
  • deserialized target. This is because there is a bug with
  • being able to treat the deserialized component as a truly
  • valid component.... I think.
  • --->
  • <cfloop
  • item="local.methodName"
  • collection="#arguments.target#">
  •  
  • <!---
  • Check to see if this method name is actually a method
  • and not a public property.
  •  
  • NOTE: You should NOT be using non-method public
  • properties with a serializable approach as they will
  • not be accessible from this wrapper component.
  • --->
  • <cfif (
  • isCustomFunction( variables.target[ local.methodName ] ) &&
  • (local.methodName neq "init")
  • )>
  •  
  • <!---
  • We need to create a proxy for this public
  • function. But, we need to see which kind of
  • method it is - that is, does it trigger a
  • serialization response.
  • --->
  • <cfif listFind( variables.triggerMethods, local.methodName, ", " )>
  •  
  • <!--- This is a proxy method with save response. --->
  • <cfset this[ local.methodName ] = variables.methodProxyWithSave />
  •  
  • <cfelse>
  •  
  • <!--- This is a standard proxy method. --->
  • <cfset this[ local.methodName ] = variables.methodProxy />
  •  
  • </cfif>
  •  
  • </cfif>
  •  
  • </cfloop>
  •  
  • <!---
  • Return this object reference. This is for method chaining
  • and for use with the NEW operator, which requires a return
  • value.
  • --->
  • <cfreturn this />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="methodProxy"
  • access="private"
  • returntype="any"
  • output="false"
  • hint="I pass this call onto the target component.">
  •  
  • <!---
  • Pass this message onto the target method with the same
  • name as the one being invoked.
  • --->
  • <cfinvoke
  • returnvariable="local.result"
  • component="#variables.target#"
  • method="#getFunctionCalledName()#"
  • argumentcollection="#arguments#"
  • />
  •  
  • <!--- Return the appropriate results. --->
  • <cfreturn variables.returnMethodProxyResult( local.result ) />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="methodProxyWithSave"
  • access="private"
  • returntype="any"
  • output="false"
  • hint="I pass this call onto the target component and then serialize the component to maintain its state.">
  •  
  • <!---
  • Pass this message onto the target method with the same
  • name as the one being invoked.
  • --->
  • <cfinvoke
  • returnvariable="local.result"
  • component="#variables.target#"
  • method="#getFunctionCalledName()#"
  • argumentcollection="#arguments#"
  • />
  •  
  • <!--- Serialize the component. --->
  • <cfset objectSave( variables.target, variables.dataFile ) />
  •  
  • <!--- Return the appropriate results. --->
  • <cfreturn variables.returnMethodProxyResult( local.result ) />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="returnMethodProxyResult"
  • access="private"
  • returntype="any"
  • output="false"
  • hint="I return the proxy method result or the current wrapper depending on the type of value being returned.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="result"
  • type="any"
  • required="false"
  • hint="I am the result returned by the target object."
  • />
  •  
  • <!--- Check to see if the result is the target object. --->
  • <cfif (
  • !isNull( arguments.result ) &&
  • isStruct( arguments.result ) &&
  • structKeyExists( arguments.result, variables.targetFlag )
  • )>
  •  
  • <!---
  • The target component is attempting to return itself.
  • Since we want to maintain the serializable behavior,
  • return this wrapper instead.
  • --->
  • <cfreturn this />
  •  
  • <cfelse>
  •  
  • <!---
  • The result is not the target object, just return
  • the given result.
  • --->
  • <cfreturn arguments.result />
  •  
  • </cfif>
  • </cffunction>
  •  
  • </cfcomponent>

When the Serializable.cfc is instantiated with the given target instance (the component being wrapped), Serializable looks to see if there is an existing, persisted version of the given component. This would be a file whose path is equal to the path of the target component plus a ".serialized" file extension. If this serialized instance exists, Serializable.cfc loads it and uses it in lieu of the target instance that we passed-in.

Once Serializable.cfc has the appropriate target instance, it then fleshes out its own method list, building it up to match the the public interface of the target component. Notice that as it does this, it uses two different types of proxy methods - those that just proxy and those that both proxy and result in a "save" of the target object state.

This kind of approach would only work with the most basic level of applications. But, in an R&D / micro-deployment kind of world, this kind of painless object persistence would be awesome! No database, no hibernate, no XML or JSON files - you simple treat the target component as if it were constantly cached in memory. And, of course, you don't need to re-instantiate it on every page request; this kind of an approach would work just as well with application-cached components.

As I was experimenting with this, I definitely came across some buggy behavior with the deserialized components; but, I'll explore those issues more deeply in future posts.




Reader Comments

I've used objectSave() and objectLoad() during development with webservices such as Twitter. I can make the real API call once bringing back all the data I need, and then serialize it to disk using objectSave(). Then I do the rest of my development that requires that data by loading it back from disk using objectLoad().

Oh, and FIRST COMMENT!

@Bob,

Oh, I like that idea. That makes a lot of sense, especially with something like Twitter where there API usage limits.

But, I'm really loving the idea of being able to use this for very simple stand-alone apps. I don't know how many of those I actually build... but I just smile thinking about it.

@Ben, returning to your original use of the word "instance" (ColdFusion instances, not Sarah instances or CFC instances):

Although there's been some talk in the dev community about CF someday supporting a "cluster scope", it hasn't materialized. Since each instance of a cluster runs in its own JVM, it's hard to visualize how such a scope could possibly work. So it remains a perpetual new feature request that we probably won't ever get.

Realizing this, I came up with a workaround that uses the new virtual file system / ram disk. I never got the go-ahead to implement it, but it was essentially using ObjectSave and ObjectLoad together with the VFS.

We save tons of "reference tables" in the server scope. For example, state table has the mapping of state codes to state names. And by "tons", I mean roughly 750 MB. (That would qualify as tons, wouldn't it?) Our caching criterion is 100 rows, with special exceptions for heavily-used tables that allow up to 200 rows. We don't cache really huge tables (counties, NAICS codes, Zip codes, etc). But the ones we do add up.

It seems excessive, but our pages are really, really fast. I've largely persuaded our developers not to use query-of-queries where a cfloop would do, so sometimes amazingly fast. But still, 750 MB... please.

Using ObjectSave and ObjectLoad together with the VFS is essentially a space-versus-time tradeoff. You liberate a ton of memory if you can accept the deserialization time hit, which is usually quite small, certainly a lot smaller than true disk I/O.

So this was just another usage suggestion. I'll let you know if my bosses ever accept it and we actually implement it.

@WebManWalking,

Whaaa? 750 MB in the server scope of cached queries? That's just bananas! I assume you're running 64bit, right? I would think with 32bit, you simply wouldn't have the RAM for so many cached goodies?

When Mark Drew was at the CFUG last month, he was also talking about the shared cluster scope. Not knowing much about Railo, I can't tell you what he said exactly; but, I think it also came down to how they would work with shared objects. So, if I stored something in two instances, would it *also* have to copy over the other objects that the shared object referenced?

I think this was in the context of CouchDB (what Mark was presenting on). What ever the exact conversation was, I got the feeling that the problem was perhaps too complex and had too many iffy questions to solve currently.

@Ben,

Yes, we're running 64-bit and 5 CF instances per server. But the decision to cache all reference tables with up to a certain size was not mine. My job is not to set policy. My job is to keep everything running within the constraints of policy.

One way to simulate a cluster scope is to use Java Messaging Service (JMS) to sync up objects on multiple servers. That's how its possible to run multiple instances in "mirrored sessions" mode (as opposed to "sticky sessions" mode). If sessions are mirrored across all instances (via JMS), it doesn't matter which instance the user's requests end up on, and you don't have to turn on sticky sessions.

The problem with that is that the memory isn't shared, it's replicated (and kept in sync). So you aren't really sharing memory, even though it seems as if you are. There's no memory-saving advantage of that kind of cluster scope as compared to simply using the server scope directly (because everything's replicated in every CF instance). There would be advantage in the automatic sync-up, but no memory saving.

If Railo has succeed at implementing a cluster scope, maybe Adobe will do it to. I'd be interested in finding out how. Maybe I should ask Mark or Ray how they did it.

@WebManWalking,

I don't want to put words in anyone's mouth. I may have totally misunderstood what they were talking about. I got the feeling they were talking about things they *couldn't* do, not things they did.

I always feel so confused by multi-server configurations. JMS sounds cool though. Perhaps one day that'll be part of my ninja skills :)

In that case, you should learn about Jini too. Jini is a service publication and discovery subsystem of Java. The CF cluster controller uses Jini to discover other instances. Then they talk to each other. Then they use JMS to sync up sticky sessions. That's also how multiserver monitoring works. Jini isn't an an acronym for anything. It's an Indo-European root word for "genie".

Outside of Java, there's another service publication and discovery system called "zeroconf". Since you have a Mac now, you should know that Apple's Bonjour is the most widespread implementation of zeroconf. That's how your Mac automatically discovers printers and other computers on your subnet. It's like a mini-DNS that doesn't require connection to the Internet. The names it associates with other IP addresses on your subnet end in ".local". So now you know where all of those .local names come from, Bonjour.

@WebManWalking,

I went to a meetup group for a product called Membase a few weeks ago. It's like a distributed, consistent memcached database. Anyway, in their setup, one of the things that I found really cool was that it seemed to auto-detect other nodes on the network. So the guy giving the presentation would just boot up another service on the machine and then the other nodes would see it. He could, at that point replicated to it and pull it into the system.

All this stuff seems very cool.

@Manithan,

I haven't tried it with anything other than a ColdFusion component; but according to the docs, it should be able to handle any complex value. Of course, I did find some buggy behavior with CFCs that I'll blog about shortly.

Windows 32 bit allows up to 2 gigs to be allotted to any single process (so instances can all be given 2 gigs).

32 Bit ColdFusion will allow 1500Mb +/- depending on other settings... allotted to the JVM. The remaining 500 are specifically reserved to other processes... such as template cache and other CF goodies.

So, 750 is only half of the 32 bit capability.

We do the same thing for a national real estate firm. Our largest single cached table is around 600ish megs, and we have a subtle maintenance system that monitors for changes and does queryAddRow... or removes rows.

At this point, QoQ joins are impractical. We let one run and it consumed 10 gigs before giving a 500 of our 12 gigs of RAM. Terribly inefficient, so yeah -- we use arrays and break things down for 'artificial' programmer made joins. Doing it in a UDF > CF's QoQ joins. We can even do 'inner joins'

Maybe I am missing something but is this working inside the ehcache engine. It would be cool if multiple servers could share objects across distributed ehcache.

@Brad,

I had not even considered the memory / performance implications of query of queries on such large caches. Yeah, I guess that could really be a problem. I suppose you have to denormalize the data to some degree - start treating it more like a data warehouse and less like a proper database model.

@Mike,

I can't speak to that. I think that when you use cacheGet() and cachePut(), it does do some sort of serialization in this vein; but, I don't know any of the details. Wouldn't want to steer you wrong.

@Mike,

You may have hit on how Railo implements the cluster scope (if it does).

On the other hand, if, as Ben suggested to you, it's serializing and replicating, the object physically resides in both JVMs, and there isn't any memory savings.

The only way you could get memory savings is if you could set aside a block of memory to be shared by the JVMs, so that cluster scope objects would reside there. Is that how ehcache works? (I'm not familiar with it.)

Seems like you could have a deadlock between two instances unless locking resided in the block too. Time to whip out Java Concurrency in Practice!

@WebMan

Sounds like a good question for the panel this week. I will ask what tags use ehcache as their caching engine. If ehcache is controlling the cached objects, which I think it is, then adding distributed remote \ shared caches is easy as changing the ehcache.xml file.

cfcache allows for you to store objects too, so I am not sure what the difference is, another good questions.

CFCache in ColdFusion 9:

?Added support for the following features:

?Caching in memory. Memory is now the default cache location.

?Caching page fragments.

?Caching specific objects, including the ability to put, get, and flush cached objects.

?Setting cache dependencies.

?Setting an idle timeout.

?Getting metadata about cached objects.

?The ability to strip white space from cached page fragments.

?The ability to throw an exception if an error occurs when flushing a cached object.

?Added get and put values of the action attribute. These values support caching of objects.

?Added dependsOn, id, idleTime, key, metadata, name, stripWhiteSpace, throwOnError, useCache, usequerystring, and value attributes.

@Mike,

Don't know what you mean when you say "the panel this week". I assume that it's something in the NYC area since that's where Ben is. Or maybe you mean an Adobe webcast. Not sure.

Anyway, I'd love to hear whatever you find out about cluster scope / multi-instance object sharing.

Afterthought: We cluster only instances on the same physical server machine. But I believe that clusters are allowed to span instances on different servers (via Jini and JMS), which is another fly in the memory sharing ointment.

@Ben and @Mike,

Man, I really wanted to go to that panel. I tried for over an hour, but apparently it requires downloading software and paying for a service. You would think, since Connect is Flash based, it ought to be possible to attend some webinars for free.

Oh well, that'll teach me not to wait till the 11th hour to figure out a webinar's communication software. Hope it went well.

@WebManWalking,

This was a typical Connect Meeting. Nothing special, or any type of payment was required.

So, I did ask about ehcache and it is engaged with cfcache tags and the second level ORM caches.

So if you want to mimic the objectsave and load you could use cacheget and cacheput which do support objects in CF9. And since ehcache supports distributed mode this could be done across a cluster.