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:
function displayName() {
return client.lastname & ", " & client.firstname;
}
Because our site makes use of the client scope to store this info, it becomes real easy to display our name from our CFM page:
<cfoutput>Greetings: #displayName()#</cfoutput>
However, what happens when we wake up and smell the coffee and discover that we don't want to use client variables? Not only do we have to change code outside the UDF but inside as well since it was tied to the client scope and not generic. A better, more usable version, would look like so:
function displayName(fn,ln) {
return ln & ", " & fn;
}
Now not only can we use it with our new session based code (#displayName(session.firstname,session.lastname)#), but we could use it to display any name on the site.
Basically we've created a method that by being more generic ends up being more useful. I've probably done a horrible job of explaining a big concept, but I think at a simple level this is a great example.
Now to get on the pulpit for a minute. I've given many presentations over my life and I've written books, blog articles, and generally have been known to run my mouth at a thousand miles per hour if someone will listen to me. I know I've said, more than once, something along the lines of:
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!
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.
Archived Comments
creating API CFC's would be a good tutorial :)
I did do a blog entry on ColdSpring and generating remote CFCs:
http://www.coldfusionjedi.c...
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. ;)
@Ray,
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.
This may a matter of style, but I typically do not have externally scoped variables in my CFC(s).
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.
Ben, Ray,
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?
@Jay: To answer your question in your code, yes, this.lclApi will be recreate on every hit, which will be a performance hit.
Yes, I did whip out a Ghostbuster quote. ;)
I believe it was a Ghost Busters quote and I believe the character "Ray" said it. I could be wrong. ;-)
Nope, not Ray, it will Bill Murray's character. Ray was Dan Akroyd(sp).
Had to look it up - Bill Murray's character was Peter Venkman.
Sorry * my previous comment was massive.
Awesome, i love ghost busters, i gotta go rent that movie again.
@Ray, so is the answer to the lclApi problem ColdSpring?
I do the same for all my remote service components. The services are all wired with ColdSpring and then application scoped and the remote facade cfc knows to call them in application scope. I don't even know if this totally breaks encapsulation since it seems to me part of the job of the remote service to know where the service it is remoting is cached and in the end, if I change how I cache the service, I am still not changing it all over my application but just in the service that requires it. (yeah...that explanation was awkward but I think you get the picture).
@Jay: Good question. As you see I strongly encouraged folks to look at ColdSpring. I don't want to say CS is "the" answer because it is just one way of solving the issue. If you have not used yet than I'm not sure I'd add it _just_ to add a remote API to an existing project.
Make sense?
Ray, I think you're confusing the concepts of encapsulation and decoupling. The example you're talking about above is actually an example of decoupling. 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.
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.
Roland - dang - yea - very good point. Is it worthwhile to edit the blog entry? I don't want to lead folks down the wrong path, and using the wrong terminology is bad.
Let me add a little addendum to the end of the blog article, and you can tell me if it clears up the problem.
Roland (and everyone else really), I did a quick update at the end. It is clear? Is it enough?
Looks good :)
Ray,
That should work.
That would then in turn be a good idea for a follow up topic: "Encapsulation, for real this time".
Another way of looking at objects is the "component" model, where each component is a black box (encapsulated), but where the black box may manage objects and entities and resources outside of itself.
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."
If you want to learn more about concepts such as encapsulation and decoupling, and why they are important, Head First Design Patterns is a good read: http://oreilly.com/catalog/...
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.
I think this IS a good example Ray as simple as it may be presented.
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.