By Jared Rypka-Hauer, www.web-relevant.com/blogs/cfobjective
Typically, I make extensive use of shared scopes in ColdFusion applications. My philosophy is "That's what they're there for!" I've seen the question come up many times before: "What's an appropriate use for shared scopes?" So, I decided to highlight a situation wherein I elected to store data in the application scope in the hopes of helping to answer the question once and for all. At the same time, I'd like to share a nifty bit of development I did and exemplify how important it is to keep all the aspects of a situation in mind when we're developing applications. But first let me say this: If you think you need to use shared scopes (application or session, primarily) then chances are you're right. How much data you can store in these scopes is really only limited by how much memory you have installed in your server. Generally, if you're wondering if you should be using the session scope for something, your best course of action is to try it, tweak it and use it ... That's what it's there for! It all started on day when I needed to tell a client how much traffic their site was getting. The host we were using at the time offered stats, but only for additional cost, and I couldn't really justify it. All I needed to know was basic information anyway: number of visitors and number of clicks. I didn't want to (or need to) mess with browser type counts, session length, entry or exit points, or any of the other extraneous statistics that a fully implemented stats package would deliver. At this point it's apparent that the solution needs to be simple, quick, and efficient. It needs to solve the problem at hand, and under these circumstances, only this problem. I needed a quick, persistent counter to track the number of visitors and the number of page views, so I just wrote one. The data is stored in the application scope, in a struct called "counter" (of all things!). Counter contains three elements: ipLog, hitCount, and visitCount. The script is very simple. It checks the incoming cgi.remote_addr value against a list in the application scope variable counter.ipLog. If the incoming IP address isn't there, it adds it and increments the visitor's counter. Regardless of whether or not the incoming IP address is found in the list, after the checking for and/or adding it, the script will increment the view counter. The first version of the counter had a serious flaw, however. Whenever the server was rebooted, I would lose the whole counter. What to do, what to do? As I was aiming for a simple and straightforward mechanism for dealing with a relatively insignificant amount of data, I chose to use WDDX. In the expanded version of the counter, after the comparisons, cfwddx converts the struct in the application scope to a WDDX packet, which is then written to disk in a file called counter.wddx. (It's amazing when things are consistent, no?) In order to make this thing work right, there was one more bit of code to add. The very first thing the final version does is check for counter.wddx. If it exists, it is read off disk and cfwddx is used to convert it back to a ColdFusion struct in the application scope. If the disk file is absent, it creates a default empty struct with the correct variety of elements. Then, and only then, does the comparison and storage process happen. Here's the interesting part: All in all, the counter code is roughly 25 lines long, counting locking and a few redundancies that I could actually remove were I to convert this to a CFC-based tool instead of inline procedural code. (You can see a copy of the code at the end of this article.) It's so simple that it was hard to think of enough to say about it while writing this! The moral of the story? ColdFusion is a powerful language, for many reasons. It's elegant, yet simple. It provides a great deal of convenience while permitting a great deal of flexibility. Refactoring and maintenance (if the design is best) are fairly simple processes. We're sitting on a powder-keg, really, just waiting for the right spark to set off an explosion of Enterprise-class web-based (and with the release of CFMX 7, non-web-based) applications. Allow me to pontificate for a minute... I LOVE writing tools. There's something about crafting a neat widget to fill a need that really gets my programmer juices flowing. I like the simplicity, and the fact that I can get my head around the whole concept in one bite. In my opinion, ColdFusion really shines when it comes to writing tools. There's no other language that can provide the same level of functionality with such flexibility, ease, and speed. Like Java a few years ago, before the development of things like SWING, the AWT, JMS, and all the other commonly available toolkits, ColdFusion is reaching a point of explosive growth in the development of amazingly portable, powerful tools. Just look at the explosion of development frameworks lately for proof... it's a brave new world. We're dealing with a platform that allows us to develop solutions like the super-simple RAM-based counter, whole development frameworks, OO applications, and anything in-between. It's a world in which I'm happy to live!<!--- If the counter struct is not defined --->
<cfswitch expression="#structKeyExists(application,'counter')#">
<cfcase value="false">
<!--- check the dir for an existing counter log --->
<cfset wddxPath = expandPath(".") & "/counter.wddx">
<cfif fileExists(wddxPath)>
<!--- if there is one, read it and deserialize it
to the application.counter struct --->
<cflock name="readWddxLock" type="readonly" timeout="5">
<cffile action="read" file="#wddxPath#$" variable="tmpWDDX">
</cflock>
<cfwddx action="wddx2cfml" input="#tmpWDDX#" output="counter">
<cflock scope="application" type="exclusive" timeout="5">
<cfset application["counter"] = counter>
</cflock>
<cfelse>
<cflock scope="application" type="exclusive" timeout="5">
<cfscript>
// Otherwise, create it from scratch
application['counter'] = structNew();
application['counter'].ipLog = ";#cgi.REMOTE_ADDR#;0.0.0.0";
application['counter'].count = 1;
application['counter'].repeats = 0; </cfscript>
</cflock>
</cfif>
</cfcase>
<cfcase value="true">
<!--- On the other hand, if there IS an existing counter log --->
<cflock scope="application" type="exclusive" timeout="5">
<cfscript>
// and the user's IP isn't listed...
increment and log the IP
if (listFind(application.counter.ipLog,cgi.REMOTE_ADDR,";") EQ 0)
{
application.counter.ipLog = ";#cgi.REMOTE_ADDR##application.counter.ipLog#";
application.counter.count = application.counter.count+1;
}
else
{
// otherwise, record a duplicate hit and move on
application.counter.repeats = application.counter.repeats+1;
}
</cfscript>
<!--- AND, always write the current counter log to a file in WDDX format --->
<cfwddx action="cfml2wddx" input="#application.counter#" output="tmpWDDX">
<cflock name="handleCounter" type="exclusive" timeout="10">
<cffile action="write" addnewline="no" file="#wddxPath#" output="#tmpWDDX#">
</cflock>
<cfset counter = duplicate(application.counter)>
</cflock>
</cfcase>
</cfswitch>