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.
public struct function getCoolRankings() {
s = {};
s["low"] = "Raymond Camden";
s["medium"] = "Ben Forta";
s["high"] = "The Fonz";
return s;
} }
component {
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.
variables.COOL_RANKINGS = { low="Raymond Camden", medium="Ben Forta", high="The Fonz" }; public struct function getCoolRankings() {
return variables.COOL_RANKINGS;
} }
component {
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.
Archived Comments
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...
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.
@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...
@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?
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.
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.
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.
@Todd
I would declare a property myself, but without the public 'setter' function. This makes it a little clearer that the value is constant.
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.
@Roe: Heh, well, now that we have 'real' constructors in CF9 - I give you that point.
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.
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!
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
@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.
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.
@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()
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.
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.
Wow - that's a freaking cool idea Elliott. How exactly is it stored though? In the compiled version of the CFC or somewhere else?
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;
}
}