Ask a Jedi: Caching "constant" structures?

This post is more than 2 years old.

Adobe evangelist Greg Wilson just asked me the following on IM and I thought it would be good to share with others. I also think it's something that folks may have strong opinions on so I'd like to see what kind of debate this can cause. Here is his question:

I've got a CFC method that returns a static structure. Basically it creates the struct on the fly and returns it. The data in the structure is all hard coded - no dynamic portion at all. Does it make sense to cache that data?

Ok, so first, let me demonstrate what he is talking about.

component {

public struct function getCoolRankings() {
	s = {};
	s["low"] = "Raymond Camden";
	s["medium"] = "Ben Forta";
	s["high"] = "The Fonz";
	return s;
}

}

In this component I've got one method, getCoolRankings. It returns a structure of labels and the person who most exemplifies the value of that label. Every time you call getCoolRankings, the structure will be recreated and returned to the caller. So the question is, would you get much benefit from caching this? Obviously the answer is yes, you would. But the savings would be so minimal that I don't think it is worth really worrying about. That being said, I did mention to Greg that I like to do things a bit differently.

ColdFusion does not have a concept of a constant, a variable that once created cannot change. But even so, I like to think of such variables as Greg's structure as a constant. I'll then create them in the constructor area of the CFC like so.

component {

variables.COOL_RANKINGS = { low="Raymond Camden", medium="Ben Forta", high="The Fonz" };

public struct function getCoolRankings() {
	return variables.COOL_RANKINGS;
}

}

As you can see, I've moved the structure into the constructor area of my CFC. I've also used an all caps name to help make it obvious that this is a special variable. Finally I modified my method to return the variable. This by itself won't cache the structure, but if the CFC itself is cached then the variable is only created once.

Finally, I also mentioned to Greg that another option would be to define the keys/data within ColdSpring. This separates the static data into your ColdSpring XML and may make for a "cleaner" separation of your CFC and the static data. I've done that a few times in the past myself.

Raymond Camden's Picture

About Raymond Camden

Raymond is a senior developer evangelist for Adobe. He focuses on document services, JavaScript, and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA https://www.raymondcamden.com

Archived Comments

Comment 1 by todd sharp posted on 9/7/2010 at 7:17 PM

I'd do the same that you did, only I'd declare the struct as a property and use accessors and call setCoolRankings() in the constructor.

But, like you said I'd actually have a SimpleConfig bean in ColdSpring that stores the static values and I'd inject that into my services since I use ColdSpring for everything...

Comment 2 by William FISK posted on 9/7/2010 at 7:18 PM

I would create this in Application.cfc.as part of the function "onApplicationStart", and would create as:

application.coolRankings = {
low = "Raymond Camden",
medium = "Ben Forta",
high = "The Fonz" };

Then just refer to application.coolRankings whenever you need to look it up.

Comment 3 by todd sharp posted on 9/7/2010 at 7:30 PM

@William... the issue with that approach - as with my ColdSpring approach, really, is that you'd then have to pass that around to all components that need access to the structure. You'd want to avoid accessing the application scope from your components if at all possible.

I'm sure you're aware of that - I just wanted to mention that is one drawback to keeping the constants outside of a component...

Comment 4 by William FISK posted on 9/7/2010 at 7:40 PM

@Todd, I must admit I do this quite a bit and I am not really aware of any disadvantage. What are the potential problems with just directly accessing, that is reading from, the application scope in a component?

Comment 5 by Raymond Camden posted on 9/7/2010 at 7:43 PM

The idea is that - in general - a CFC should not "reach out" of itself. What if you change the name of the application variable? All of a sudden your CFC breaks. But if you had passed in the value to the CFC, then it would not matter. Basically - less cohesion is normally a better thing.

To be clear - I definitely DO hit the app scope from CFCs. This is especially true of CFCs I use to "respond" to Ajax stuff.

Comment 6 by William FISK posted on 9/7/2010 at 7:51 PM

Ok, I see what you're saying. In fact I would probably wrap the access to application.coolRankings in a function called say "findCoolRanking". And then "include" that function in my component.

But still in any application I do have a number of fairly straightforward set of simple lookups, such as "coolRankings" that I do not want to store in database tables (no point) and so simple store them all as different structs in the application scope, with perhaps a function to access it.

I was just worried that I might be risking some sort of "race" condition or other weird error by accessing application directly from a component.

Comment 7 by Raymond Camden posted on 9/7/2010 at 7:54 PM

In theory if you refresh the app scope manually (like with a URL hook), you could get a race condition. Ie, line one makes the struct. Another request tries to use it before line 2 sets the values. However, that should be something you don't have to normally worry about.

Comment 8 by Ryan McIlmoyl posted on 9/7/2010 at 8:00 PM

@Todd

I would declare a property myself, but without the public 'setter' function. This makes it a little clearer that the value is constant.

Comment 9 by Roe posted on 9/7/2010 at 8:40 PM

All cfset's must be inside a function in my components, anything else is evil and all perpetrators will be flogged.

;-)

That being said I would also declare the data in coldspring via properties and corresponding mutators. Also doing it this way "caches" the data in the App Scope so it's a 2x win.

Comment 10 by Raymond Camden posted on 9/7/2010 at 8:43 PM

@Roe: Heh, well, now that we have 'real' constructors in CF9 - I give you that point.

Comment 11 by Matt Gersting posted on 9/7/2010 at 9:30 PM

Just slightly off-topic, this reminds me of one of the coolest features the Railo folks showed at CFUNITED this year - method caching. Just in the same way you can cache a cfquery, you can add cachedWithin, etc. to a cfc function.

Comment 12 by Gareth Arch posted on 9/7/2010 at 11:12 PM

Ha, great timing on this one. Just had a need for using some constants in a CFC for an API. Just reworked it slightly based on some of the suggestions here :)
Thanks all!

Comment 13 by Dominic Watson posted on 9/8/2010 at 12:01 PM

If the constants are related to a component, we are declaring them in that component as *public* properties, i.e.:

<cfset this.SOME_CONSTANT = 5734 />
<cfset this.SOME_OTHER_CONSTANT = 454 />

This is an attempt to mimic javas "public static final" style constants (except that they won't be static, or final!). With the example above, we may then have some code such as:

myObj = CreateObject('component', 'myObj').init();
someValue = myObj.someMethod( myObj.SOME_CONSTANT );

Dominic

Comment 14 by Roe posted on 9/8/2010 at 11:59 PM

@Dominic

In that case I'd do this:

public struct function getSomeConstant() {
return 5734;
}
public struct function getSomeOtherConstant() {
return 5734;
}

At least here you can be assured that the value wont change.

Comment 15 by Dominic Watson posted on 9/9/2010 at 3:03 PM

Yeh, that works nicely. We chose not to do that to closer mimic a Java *style* of code - i.e. someClassHas.SOME_CONSTANT. Splittling hairs really.

Comment 16 by Gareth Arch posted on 9/9/2010 at 7:54 PM

@Dominic,
I think it's more that you have now made the values public and can be altered, rather than keeping them private and "semi-constant-like", even though you can access them similarly to Java. Perhaps a mix of the 2?

variables.CONSTANTS.SOME_CONSTANT = "myPrivateSomeConstant";

public string function SOME_CONSTANT() { return variables.CONSTANTS.SOME_CONSTANT }

Then in the calling page it would be slightly modified
someClassHas.SOME_CONSTANT()

Comment 17 by Dominic Watson posted on 9/9/2010 at 8:12 PM

I'm well aware that the value is publicly writable - just comfortable with that less-than-perfectness. To my style delactation, this convention screams constant at me:

someObj.SOME_VALUE

This less so:

someObj.getSomeValue()

I'm happy to allow the flaw.

Comment 18 by Elliott Sprehn posted on 9/10/2010 at 8:58 PM

If you want to go one step further and avoid recreation of the structure for every object instantiation store it in the metadata for the component. This gets you maximum reuse, even better than storing it in the application scope and doesn't violate the encapsulation rule Ray was talking about.

component {
//...

metadata = getMetaData(this);
if( not structKeyExists(metadata,"COOL_RANKINGS") ) {
metadata.COOL_RANKINGS = ...;
}

//...
}

Note that you don't even need locking since you're not going to be modifying the value.

Comment 19 by Raymond Camden posted on 9/10/2010 at 9:01 PM

Wow - that's a freaking cool idea Elliott. How exactly is it stored though? In the compiled version of the CFC or somewhere else?

Comment 20 by Eric Belair posted on 7/1/2011 at 9:58 PM

What I've done is store a Structure of Constants in a UDF Library (I user the Ben Nadel methodology to store/reuse UDFs - http://www.bennadel.com/blo... which also contains Utility Functions related to my application. I often need to access all of the Constants as a List or Array or Query, so I simply have get() functions to get the individual values, or the Struct/Array/List/Query version that I need:

component {
// Constants
VARIABLES._COOL_RANKINGS = {
ultraLow = "Eric Belair",
low = "Raymond Camden",
high = "Ben Forta",
supreme = "The Fonz"
};

function getCoolnessList() {
return Request.UDFLib.Struct.StructValuesToList(VARIABLES.COOL_RANKINGS);
}

function getCoolRankingsQuery() {
return Request.UDFLib.Array.simpleArrayToQuery(StructValuesToArray(VARIABLES.COOL_RANKINGS));
}

function getUltraLowCoolness() {
return VARIABLES.COOL_RANKINGS.ultraLow;
}

function getLowCoolness() {
return VARIABLES.COOL_RANKINGS.low;
}

function getHighCoolness() {
return VARIABLES.COOL_RANKINGS.high;
}

function getSupremeCoolness() {
return VARIABLES.COOL_RANKINGS.supreme;
}
}