Skip to main content
Ben Nadel at cf.Objective() 2011 (Minneapolis, MN) with: Alec Irwin
Ben Nadel at cf.Objective() 2011 (Minneapolis, MN) with: Alec Irwin

ColdFusion 10 - Selectively Exposing ColdFusion Component Behaviors With Closures

By
Published in Comments (5)

Last night, I was watching Douglas Crockford's YUI presentation, "Principles of Security." In his talk, Crockford discusses safe "Object Systems" in which objects are given as little authority as possible in order to do their work; "as it turns out, good design uses information hiding as well as capability hiding," (Crockford, 45:24). In order to limit the exposure of object behaviors, intermediary objects - or Facets - can be used. As I was watching this, it occurred to me that the Closures added in ColdFusion 10 could be used to create light-weight Facets on the fly.

NOTE: At the time of this writing, ColdFusion 10 was in public beta.

To explore this limited-capability concept of safe Object Systems, I am going to create a ColdFusion component that represents a financial account. This component will have the following behaviors:

  • Account::credit( amount )
  • Account::debit( amount )
  • Account::getBalance()

Then, using ColdFusion 10 closures, we're going to create a Facet of this object that only exposes the credit() and getBalance() methods - cause, after all, who wants their account debited?

First, let's take a look at the Account.cfc ColdFusion component:

Account.cfc - Our Financial Account Domain Model

<cfscript>
// NOTE: CFScript tag added purely for Gist color-coding. Remove!

component
	output="false"
	hint="I provide account related functionality"
	{


	// I return an initialized component.
	function init( Numeric startingBalance = 0 ){

		// Store the initial balance of the component.
		variables.balance = startingBalance;

		// Return this object reference.
		return( this );

	}


	// I credit the account the given amount (adding money to the
	// account balance).
	function credit( Numeric amount ){

		// Add money to the account.
		variables.balance += amount;

		// Return this object reference for method chaining.
		return( this );

	}


	// I debit the account the given amount (removing money from
	// the account balance).
	function debit( Numeric amount ){

		// Remove money from the account.
		variables.balance -= amount;

		// Return this object reference for method chaining.
		return( this );

	}


	// I get the current balance.
	function getBalance(){

		// Return the current balance amount.
		return( variables.balance );

	}


}

// NOTE: CFScript tag added purely for Gist color-coding. Remove!
</cfscript>

Clearly, the component is quite limited, as is; but, now, we're going to create an even more limited version by creating a light-weight, on-the-fly Facet object that exposes a subset of the Account's instance methods. The object factory for this Facet is a function called exposeBehaviors(). It takes the target object and a list of methods to expose and returns a new object reference.

<cfscript>


	// I return a new object with only the given method names expsed
	// as behaviors on the resultant object.
	function exposeMethods( target, methods ){

		// Define the behavior that will be exposed. This method will
		// act as a proxy to the underlying behavior which will pass
		// the message onto the target component and return the value
		// returned from the target component.
		var behavior = function(){

			// Get the name of the function being invoked.
			var methodName = getFunctionCalledName();

			// Invoke the method on the taret object and return the
			// result to the calling context.
			return(
				invoke( target, methodName, arguments )
			);

		};

		// Create the object we are going to return.
		var proxy = {};

		// For each method name, expose the behavior through the
		// proxy object.
		arrayEach(
			methods,
			function( methodName ){

				// Expose the behavior.
				proxy[ methodName ] = behavior;

			}
		);

		// Return the new proxy object with exposed behaviors.
		return( proxy );

	}


	// ------------------------------------------------------ //
	// ------------------------------------------------------ //
	// ------------------------------------------------------ //
	// ------------------------------------------------------ //


	// Create a new account component instance.
	account = new Account( 100 );


	// Now, let's create a proxy to the given account component that
	// only exposes the credit and the balance methods but not the
	// debit method.
	goodAccount = exposeMethods(
		account,
		[ "credit", "getBalance" ]
	);


	writeOutput( "Account credited [50]. <br />" );

	// Credit the account.
	goodAccount.credit( 50 );


	// Now, I know this is going to fail, but for funzies, let's try
	// to call the non-exposed behavior - debit() - on the proxy.
	try {

		writeOutput( "Trying to debit()." );

		// Try to invoke the debit() method - a non-exposed behavior.
		goodAccount.debit( 25 );

	} catch( Any cfcatch ){

		writeOutput( " [ FAILED ] <br />" );

	}


	// Now, let's call the exposed behavior to get the balanace.
	writeOutput( "Balanace: " & goodAccount.getBalance() );


</cfscript>

As you can see, the exposeMethods() function uses ColdFusion 10 closures and the getFunctionCalledName() function in order to define the proxy methods. You could create a new function for each proxied method; but for this proof-of-concept, I thought the reuse of a single method felt more elegant. Once we have our proxy object, we then try to call all three methods on the proxy object, knowing that one of the methods - debit() - was not actually exposed.

When we run the above code, we get the following page output:

Account credited [50].
Trying to debit(). [ FAILED ]
Balanace: 150

As you can see, the credit() and getBalance() methods worked; but, the hidden method, debit(), could not be invoked on the Facet (proxy object). In essence, we were hiding the debit "capability" of the underlying ColdFusion component.

This approach works because the new Closures in ColdFusion 10 allow lexical variable references to be maintained. This means that even after the proxied behavior methods are passed out-of-scope, they still maintain a reference to the "target" variable which was passed into the exposeMethods() factory function.

Closures are the awesome! I really think they've brought ColdFusion, as a language, to the next level. I'm super excited to see how people start leveraging them.

Want to use code from this post? Check out the license.

Reader Comments

52 Comments

Do you have a real world use case on why you wouldn't just make the methods you didn't want exposed hidden? Just curious as to the "Why" here. Other than that, cool stuff!

15,902 Comments

@Dan,

I haven't really ever seen this kind of thing in ColdFusion; but, I see it in JavaScript when passing objects into other objects. You create these "sandbox" objects that only expose certain pieces of functionality.

I don't have any great experience with that, either, though :D

But, at a philosophical level, it's not really a matter of public vs. private methods - it's about exposing ONLY the functionality that you need to expose in a given context. So, imagine you have a component that can do a whole lot of stuff; but, when you use it one context, ONLY a few of those methods need to be available. If you allow ALL of the functions to be accessible, then you can create a security or maintenance problem. If you can, however, selectively expose only the methods that need to be accessed, you can be sure that the calling code cannot misuse the component.

... theoretically :)

8 Comments

Very cool example -- the syntax is definitely going to take some getting used to in CFML.

In this example, could you not achieve the same result by having Account contain all three public methods, and then a CreditOnlyAccount extend Account and override debit() with a private version (or something)....

Obviously the point of the example was to demonstrate how closures could accomplish this, but I'm wondering if there's any functional difference between using the closure and simply doing it OOP-style...or if there are cases which will only work properly (or cleanly) in one of the two?

15,902 Comments

@Joe,

You can definitely do this in a Object Oriented style. In his talk, Crockford mentions that this kind of approach is especially easy in a "class-free" language like JavaScript. So, I think it's not much that you can or can't do it in one language - I think it's more like taking advantage of the features that a language provides to make certain tasks more straightforward.

This way, you can custom-tailor objects per "injection" rather than by abstract type.

Of course, I am getting all of this from one presentation, so take with a grain of salt.

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel