Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at CFinNC 2009 (Raleigh, North Carolina) with:

Using A Sandbox To Decouple ColdFusion Components In A Modular Application Architecture

By Ben Nadel on
Tags: ColdFusion

In the last few posts, I looked at using Closures in ColdFusion 10 to limit access to a component's methods. This path of exploration was based on a Crockford presentation that I saw on JavaScript. Along the same lines, I've read a good number of blog posts that describe using a "Sandbox" as a way to keep JavaScript modules decoupled from each other as well as loosely coupled to the application in which they reside. I've never really used a sandbox object in JavaScript or ColdFusion; so, I thought this would be a fun learning extension of the previous two posts.

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


 
 
 

 
  
 
 
 

Typically, in a ColdFusion application, I pass component references into the init() method of any component that needs them. So if component-B needs a reference to component-A, I'll pass component-A into component-B at the point of instantiation:

  • componentB = new ComponentB( componentA );

This helps keep my ColdFusion components loosely coupled from the application because they never break encapsulation when accessing functionality; that is, they never have to reach beyond their instantiation arguments to invoke non-local functions. This is nice; but, it still couples one component to another. Not only do we have to pass-in all the required components, we have to make sure that the required component APIs don't change.

A Sandbox object can alleviate both of these problems. A Sandbox object allows for method renaming as well as the merging of multiple points of functionality. As far as I understand it (which is really very little at this point), a Sandbox object is a component's only point of contact with the outside world: the component can only talk to itself; or, it can talk to the Sandbox.

To explore this architecture, I have a BlogService.cfc ColdFusion component that requires several other components in order to function properly. And, as you'll see in the code below, I never pass those dependencies into the BlogService.cfc instance; rather, I create a Sandbox object that proxies the dependencies, providing a single reference and single API.

  • <cfscript>
  •  
  •  
  • // Set up the domain model for some of the components. For this
  • // demo, let's assume that these components are super simple and
  • // don't need any special configuration.
  • securityService = new SecurityService();
  • emailService = new EmailService();
  • logService = new LogService();
  •  
  •  
  • // ------------------------------------------------------ //
  • // ------------------------------------------------------ //
  •  
  •  
  • // Next, we're gonna set up the Blog Service domain model. For
  • // this, we're going to set up a sandbox object - this is the
  • // Blog Service's connection to the world outside itself. It is
  • // the only way that the blog service can interact with the
  • // application at-large (of which it really doesn't know
  • // anything).
  • sandbox = {};
  •  
  • // Set up a sandbox method - pass off to Security Service.
  • sandbox.isBlackListedComment = function(
  • targetName,
  • targetUrl,
  • targetIP
  • ){
  •  
  • return(
  • securityService.isBlackListedName( targetName ) ||
  • securityService.isBlackListedUrl( targetUrl ) ||
  • securityService.isBlackListedIP( targetIP )
  • );
  •  
  • };
  •  
  • // Set up a sandbox method - pass off to Email Service.
  • sandbox.sendCommentEmail = function( users, subject, content ){
  •  
  • return(
  • emailService.sendBlogCommentEmail( users, subject, content )
  • );
  •  
  • };
  •  
  • // Set up a sandbox method - pass off to Log Service.
  • sandbox.logComment = function( name, comment ){
  •  
  • return(
  • logService.logBlogComment( name, comment )
  • );
  •  
  • };
  •  
  •  
  • // ------------------------------------------------------ //
  • // ------------------------------------------------------ //
  •  
  •  
  • // Instantiate the Blog Service and pass-in the Sandbox object
  • // that we created above; this sandbox allows the blog service
  • // to communicate with the greater appliation when necessary.
  • // BUT, only the most limited way possible to do the job.
  • blogService = new BlogService( sandbox );
  •  
  • // Save a blog comment - this process may involve calls to the
  • // sandbox object for security checks, logging, and email.
  • blogService.addComment(
  • name = "Tricia",
  • email = "tricia@test.com",
  • comment = "Hey Ben, what it be like?!"
  • );
  •  
  •  
  • </cfscript>

Due to limitations in the ColdFusion 10 parser / compiler, I cannot instantiate my BlogService.cfc ColdFusion component and define the Sandbox object in one step; rather, I have to define the Sandbox object first and then pass it into the BlogService.cfc during instantiation.

As you can see, though, the Sandbox object doesn't really provide any "new" functionality; rather, it acts as a proxy to several other components. This layer of abstraction completely decouples the components from each other - now, the BlogService.cfc is only coupled the Sandbox. This means that as the dependencies change, the internal logic of the Sandbox can be changed to shield the BlogService.cfc.

I think that this probably makes the creation, testing, and maintaining of ColdFusion components easier because you only have to worry about one point of contact - you only have to worry about one point of coupling. The downside is that it definitely requires more code and more forethought about how your ColdFusion components will work.

I'm really outside my comfort zone here, in terms of application architecture; so, all responses, good and bad, are definitely welcome.



Reader Comments

Ben,

This is interesting as an exercise for playing with the new features, but personally, I'd argue against this practice (at least certain aspects of it).

The method injection is actually a pretty neat feature, and runtime method injection is something that a lot of people use and swear by.

The biggest issue I would take with the above code is that you really should have a concrete Interface defined to pass into the BlogService constructor, and the sandbox object should conform to that interface. The reason this is necessary is that if you don't define the interface, then you can pass ANY object to the BlogService. More importantly, you could pass an object to the BlogService that is missing methods that it needs. This leads to a situation where in your calling code, you need to use reflection (which is easy in CF) before calling any method which has been generated in this manner.

I know you can already delete methods on objects and break things pretty easily in CF, but in this case, where you are generating objects and adding and removing methods as a modus operandi, it could become more pronounced. If you really wanted to ensure the contract between the objects, you would be forced to do it at runtime, with code that looks like this:

  • component BlogService;
  •  
  • init(sandbox) {
  • variables.sandbox = sandbox;
  • }
  •  
  • function addComment(name, email, comment) {
  • if (structKeyExists(sandbox, "isBlackListedComment") and isCustomFunction(sandbox.isBlackListedComment)) {
  • sandbox.isBlackListedComment(comment);
  • //....
  • }
  •  
  • if (structKeyExists(sandbox, "sendCommentEmail") and isCustomFunction(sandbox.sendCommentEmail)) {
  • sandbox.sendCommentEmail(comment);
  • //....
  • }
  •  
  • //etc....
  • }

You wind up paying the price pretty heavily in your BlogService for being able to dynamically rearrange your objects.

Reply to this Comment

@Roland,

I definitely see what you're saying. I wouldn't be against moving the "Sandbox" into its own CFC. If for no other reason, it would definitely make the code smaller. In my example, I am only dealing with *one* component. Now, imagine that inline-sandbox-creation for a dozen components?

Reply to this Comment

This is a cool concept, but I'm having trouble imagining how I would use it. If I understand correctly, it seems like the sandbox is ultimately providing an API to your securityService, emailService, and logService APIs?

I agree with CJM, I'd like to see some more information about designing code in this fashion ( in any language).

Reply to this Comment

@Sean, @CJM,

I don't personally have any real-world experience with this kind of programming, either on the server-side or the client-side. The only place I've ever seen it really discussed in any detail is for client-side programming. Maybe this is because UI-based modules are made to be more modular / reusable than client-side modules? Perhaps that requires a larger separation of concerns.

Here is an article by the great Addi Osmani that discusses large scale JavaScript application architecture. He touches on Sandbox usage:

http://addyosmani.com/largescalejavascript/

Nicholas Zakas also mentions this stuff in a number of articles / presentations, but I cannot find a link at this time.

All to say, for me, this is just experimentation. I can't really argue pros/cons from a real-world standpoint. As always, definitely open to any ideas.

Reply to this Comment

@Ben,

Thanks for the link, I love Addy Osmani's stuff and this one is no exception. The "sandbox" pattern was having trouble clicking with me until I saw him call it by another name - "facade."

Because I work primarily with server-side stuff, I'm used to seeing the facade as an individual class rather than being defined the way you did it.

What I think is really cool about this demo - although I'm still having trouble coming up with a use case - is that you've essentially created a CFC (commentFacade AKA sandbox) without actually having to define it in a physical file (commentFacade.cfc).

Reply to this Comment

The example given is really just a Facade / Adaptor / Flyweight design pattern.

Basically, object A expects to talk to an object of type B, but you only have an object of type C, so you create a "facade" that looks like a B, but translates parameters and results to and from C.

BTW, if you haven't read Design Patterns: Elements of Reusable Object-Oriented Software, you should drop everything and do it now... (grin)

(Tried to provide link, commenting system blocked it...)

Reply to this Comment

@Sean, @Michael,

I did read the Design Patterns book years ago; way before I really knew much about anything. I should probably read it again now that my view on the world has changed so much and I have a few years under my belt.

In addition to simplifying things, which I think is what a Facade does??? I like the fact that this completely decoupled one component from the other components that are hidden behind the Facade. Just seems cool!

Reply to this Comment

@Ben, a Facade traditionally presents a simpler or easier to use interface.

An Adaptor is used when an object (A) expects to use an object with a certain interface (B), and the object you have (C) doesn't conform.

The Adaptor wraps a B interface around object C, translating the requests and results from and passed back to object A.

That's what you implied with, "This means that as the dependencies change, the internal logic of the Sandbox can be changed to shield the BlogService.cfc."

If you're going to do it, however, you should spend some thought on what might be an optimum implementation. I've seen too many Adaptors where you're expected to pass in the specific structures and constants required by a specific implementation, and which make actually changing the underlying implementation all but impossible.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.