There was an interesting blog post this morning on Ben Nadel's site (A Serious CFThread Bug in ColdFusion?) that lead to a discussion about CFC methods and encapsulation. This is a topic I've covered before but I think it bears repeating with some good examples. We all know that we shouldn't break encapsulation in methods, but why? And is there ever a good reason to break encapsulation?
First let's describe what we mean by encapsulation. In general, a CFC method (or even a UDF or a custom tag) should have no connections to code outside of itself. Here is a very simple example. Imagine we have created a method that will print a name as: Lastname, First. We could easily write up a UDF for our site like so:2 return client.lastname & ", " & client.firstname;
3 }
2 return ln & ", " & fn;
3 }
The truth is, though, that this isn't really true. I'd be hard pressed to find any rule in ColdFusion (or development in general) that is either 100% true all the time or as 'fatal' as some folks may think. That being said, there is a good counter example to the 'rule' above. Remote service APIs. I'll use "Remote Service APIs" as a generic term for any CFC created specifically for remote clients. For example, you may build a CFC that others can call to get the latest product data from your site. That CFC may have a method that gets products and converts the result set to XML. Each time you call a CFC remotely, ColdFusion has to create the CFC from scratch. Imagine if your site already had a products CFC cached in the Application scope. You could create a new instance of this CFC in your remote API CFC. Ok, but what if you need to work with 10 CFCs in order to create the XML? What if you change how a CFC is created and update Application.cfc but forget to update your remote API? Not only do you have performance issues to worry about now you also have configuration issues as well! In this case I'd suggest that the simplest thing to do is to let your remote API cfc simply reuse the existing Application scoped CFCs. You then don't have to worry about configuration (see note below) and you get the benefit of not having to recreate additional CFCs on each remote call. Seem reasonable? Any other good counterexamples to the "don't break encapsulation" rule? p.s. When it comes to CFC configuration, please do not forget ColdSpring. ColdSpring not only makes complex CFC configuration incredibly simple it can also automatically generate remote service APIs that will reuse your CFCs. Updated 1:41PM: I want to point out an important comment by Roland Collins. He correctly pointed out that I confused the topic of encapsulation with decoupling. He said: "Decoupling is about making sure that objects don't have dependencies on other objects, as in your example above. Encapsulation is more about making sure that an object provides a coherent and complete interface so that objects that call it do not have to have any idea how it works internally." Hopefully I haven't led people down the wrong path here, and I apologize for the mistake.If you do X, even once, you will regret it and the world will come to an end. Human sacrifice, dogs and cats living together... mass hysteria!


Comment 1 written by johan on 22 January 2009, at 10:02 AM
Comment 2 written by Raymond Camden on 22 January 2009, at 10:05 AM
http://www.coldfusionjedi.com/index.cfm/2008/8/22/...
I could do a simpler, non-framework one as well if folks think it makes sense. It's hard to NOT use CS/Model-Glue when I'm so used to it. ;)
Comment 3 written by Ben Nadel on 22 January 2009, at 10:17 AM
Good post. I always have to remember that what doesn't bend, breaks :)
@johan,
For me, when I create an API-only CFC for remote invocation, I usually just set my "instance" variables inside of the pseudo constructor (in CFcomponent, before first CFFunction). These variables usually work the same way your Init() method, only they refer to hard-coded APPLICATION scoped variables.
Comment 4 written by Teddy R. Payne on 22 January 2009, at 12:49 PM
I too use the pseudo constructor approach. If the values of the psuedo constructor need to be reffered to from outside of the CFC, I would pass those values into the the init() function that would then in turn populate those values into the variables scope.
I just don't like having to depend on a particular given scope to use my CFC(s) as I could receive that information from complex type friendly scopes.
Ray's second approach, at a basic level, is related to that idea.
Comment 5 written by Jay on 22 January 2009, at 1:06 PM
Great work here guys, Ben's post on threading is epic. Threading still makes my head spin, adds a third dimension to programming.
Ray, I personally would love a simpler, non framework post on API-only CFCs. I recently created one and felt a little unsure. I ended up with something like this:
<cfcomponent>
<cfset this.baseURL = "http://www.yahoo.ca/foo" />
<cfset this.DSN = "mydsn" />
<cfset this.logfilename = "logApiWebService" />
<!--- local complonent, being paranoid but i want to hide db code from the front facing cfc, not sure if this is re-created on every webservice call--->
<cfset this.lclApi = CreateObject("component","foo.apiPrivateMethods").init(this.DSN)
/>
<cffunction name="getUserDetails" access="remote" returntype="xml" output="false">
....
</cffunction>
</cfcomponent>
PS My 80s movie trivia is rusty but did Ray just break out a ghostbusters quote?
Comment 6 written by Raymond Camden on 22 January 2009, at 1:10 PM
Yes, I did whip out a Ghostbuster quote. ;)
Comment 7 written by Teddy R. Payne on 22 January 2009, at 1:11 PM
Comment 8 written by Raymond Camden on 22 January 2009, at 1:18 PM
Comment 9 written by Raymond Camden on 22 January 2009, at 1:22 PM
Comment 10 written by Jay on 22 January 2009, at 1:23 PM
Awesome, i love ghost busters, i gotta go rent that movie again.
@Ray, so is the answer to the lclApi problem ColdSpring?
Comment 11 written by Brian Rinaldi on 22 January 2009, at 1:26 PM
Comment 12 written by Raymond Camden on 22 January 2009, at 1:29 PM
Make sense?
Comment 13 written by Roland Collins on 22 January 2009, at 1:29 PM
In fact, in the example above, the first function is fully encapsulated - the caller has no idea how the function works internally. It is, however tightly-coupled with the application scope.
Comment 14 written by Raymond Camden on 22 January 2009, at 1:36 PM
Let me add a little addendum to the end of the blog article, and you can tell me if it clears up the problem.
Comment 15 written by Raymond Camden on 22 January 2009, at 1:43 PM
Comment 16 written by Roland Collins on 22 January 2009, at 1:57 PM
Comment 17 written by Teddy R. Payne on 22 January 2009, at 1:57 PM
That should work.
That would then in turn be a good idea for a follow up topic: "Encapsulation, for real this time".
Comment 18 written by Michael Long on 22 January 2009, at 3:58 PM
One example of this would be the fairly common practice of creating "beans" as interfaces to data. At least one framework that I know of creates beans to manage scopes like the session scope and the request scope.
The bean provides a common "get/set" interface to these external resources and as far as anyone using the bean knows, the data accessed by the bean could be stored anywhere. Internally, however, a session bean manages data in a session scope that exists outside of itself.
A planned side effect, if you will. And one where the object and the data are separate but tightly coupled. Swap in a "client" bean, and the rest of the system won't care. Swap that bean with one that communicates with a app server, and it still shouldn't care.
One needs to know WHY the rule exists, what it's intended to accomplish, and when to apply it... and when to ignore it.
Or in the spirit of the article, "... they're more like guidelines than rules, actually."
Comment 19 written by Martijn van der Woud on 23 January 2009, at 2:58 AM
The book is meant for Java developers and the code examples are in Java, but Java is not that hard if you have worked with CFCs and cfscript extensively.
Comment 20 written by Hussein Grant on 26 January 2009, at 10:45 AM
Funny thing is, I always felt uncomfortable referencing cf scopes directly inside my methods and therefore made a conscious effort not too where ever possible. I think the cross over from procedural to OOP style development brings along some bad habits and it does take time to part with the old baggage. It's good to know that my thinking wasn't far off at all.
[Add Comment] [Subscribe to Comments]