ColdFusion 10 - Selectively Exposing ColdFusion Component Behaviors - Part II

Posted April 12, 2012 at 10:34 AM by Ben Nadel

Tags: ColdFusion

Yesterday, I looked at using ColdFusion 10's Closure syntax as a light-weight, class-free way to expose selective behaviors on ColdFusion components. This was inspired by a Douglas Crockford presentation that talked about giving objets only as much authority as they need to get the job done - and no more. To continue my exploration of this new topic, I wanted to look at it again, this time with a slightly more real-world use-case in mind.

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

For this demo, imagine that I have an online eCommerce website. Now, also imagine that this website needs to send out a variety of formatted, HTML emails to its customers. In order to simplify the creation, maintenance, and delivery of these emails, the actual rendering of the email content has been factored-out into its own ColdFusion component: EmailService.cfc. For this eCommerce site, this EmailService.cfc component has the following methods:

  • sendInvoiceEmail()
  • sendNewsletterEmail()
  • sendPasswordResetEmail()
  • sendWelcomeEmail()

As you can see, based on the method names, this EmailService.cfc handles a variety of emailing needs and circumstances.

Now, imagine that part of this eCommerce site has a shopping cart whose behavior is modeled by and encapsulated within another ColdFusion component: ShoppingCart.cfc. For the sake of exploration, let's assume that when the customer goes to checkout, the ShoppingCart.cfc ColdFusion component takes care of the order processing and sending out the order invoice. In order to send out the order invoice, the ShoppingCart.cfc needs a reference to the EmailService.cfc. However, the shopping cart doesn't need access to every possible email; in fact, the shopping cart only needs access to one of the email methods - sendInvoiceEmail().

In an effort to expose only the functionality that is required by the ShoppingCart.cfc component, we can use closures to create an on-the-fly proxy to the EmailService.cfc that only exposes the sendInvoiceEmail() method. By doing so, we limit the authority of the shopping cart to only the features that it is meant to have.

  • <cfscript>
  •  
  •  
  • // I return a new object with only the given method names
  • // exposed as behaviors on the resultant object. During the
  • // proxy creation, the method names can be renamed if a struct
  • // of names-pairs is passed in.
  • function exposeMethods( target, methods ){
  •  
  • // Create our method name mapping. In this collection, the
  • // key will be the original method name and the value will be
  • // the method name to be used in the proxy object.
  • var methodNameMap = {};
  •  
  • // Check to see if the methods collection is an array or a
  • // struct. If it's an array, then we want to convert it into
  • // a name-map with a direct translation so that we can treat
  • // the proxy creation in a uniform manner.
  • if (isArray( methods )){
  •  
  • // Convert each method into a direct mapping.
  • for (var methodName in methods){
  •  
  • methodNameMap[ methodName ] = methodName;
  •  
  • }
  •  
  • // If the methods collection is not an array, we will assume
  • // it is a struct.
  • } else {
  •  
  • // Simply use this struct as the name map.
  • methodNameMap = methods;
  •  
  • }
  •  
  • // Create the proxy object that we are going to return.
  • var proxy = {};
  •  
  • // Loop over each exposed method to create a named proxy
  • // method. We are going to be creating a new method for each
  • // method that we are proxying.
  • structEach(
  • methodNameMap,
  • function( originalMethodName, proxyMethodName ){
  •  
  • // Define the proxy method with the new name. This
  • // will simply pass the method message onto the
  • // target object.
  • proxy[ proxyMethodName ] = function(){
  •  
  • // Invoke the method on the taret object and
  • // return the result to the calling context.
  • return(
  • invoke( target, originalMethodName, arguments )
  • );
  •  
  • };
  •  
  • }
  • );
  •  
  • // Return the new proxy object with exposed behaviors.
  • return( proxy );
  •  
  • }
  •  
  •  
  •  
  • // ------------------------------------------------------ //
  • // ------------------------------------------------------ //
  • // ------------------------------------------------------ //
  • // ------------------------------------------------------ //
  •  
  •  
  •  
  • // Create an instance of our email service.
  • emailService = new EmailService();
  •  
  • // Create an instance of our shopping cart. The shopping cart
  • // needs to be able to send out an email; BUT, it does not need
  • // access to all kinds of emails - just the one kind of email
  • // that will go out after a user has checked-out from shopping.
  • shoppingCart = new ShoppingCart(
  • emailService = exposeMethods(
  • emailService,
  • {
  • sendInvoiceEmail: "sendInvoice"
  • }
  • )
  • );
  •  
  • // ...
  • // ... user does some shopping. ... //
  • // ...
  •  
  • // Now that the user is done, let's process the order. This will
  • // process the order and send out an Invoice to the current
  • // customer using the composed EmailService instance.
  • shoppingCart.processOrder();
  •  
  •  
  • </cfscript>

In this experiment, not only am I creating a limited proxy to the EmailService.cfc, I am also renaming the sendInvoiceEmail() method during the translation. This was done mostly to demonstrate that these proxy objects can have an API that doesn't match the target object. Not only does this approach grant a minimal amount of authority, it also makes the coupling between the two components more flexible since this intermediary sandbox can easily absorb any name changes.

I'm not a good enough Application Architect to say whether this is a good approach or a bad approach - for me, this is just a fun exploration. I can say, however, that on the JavaScript side of things, I do see more and more "really smart people" using intermediary objects as a way to expose "sandboxed" communication between decoupled components. As such, I think it's definitely worth looking into a bit more.


You Might Also Be Interested In:



Reader Comments

Apr 12, 2012 at 11:23 AM // reply »
8 Comments

Very cool.

Now if I could just get it working by configuring in ColdSpring I could easily (further) secure several boundaries in an application... actually, I wonder if this might be a good way to 9re)implement the remoteMethodNames property of RemoteFactoryBean ...


Apr 12, 2012 at 3:16 PM // reply »
11,238 Comments

@Tom,

Can't say that I really know anything about ColdSpring, but glad you found this interesting :)


Apr 12, 2012 at 5:47 PM // reply »
11,238 Comments

@All,

Trying to take this concept one step further to use a "Sandbox" object as a way to further abstraction and enhance decoupling:

http://www.bennadel.com/blog/2363-Using-A-Sandbox-To-Decouple-ColdFusion-Components-In-A-Modular-Application-Architecture.htm

Now, I'm really far outside my comfort zone; but, interesting concepts.


Apr 12, 2012 at 6:45 PM // reply »
4 Comments

Hey Ben!

What settings do you use for GIST?

-Rich


Apr 12, 2012 at 6:48 PM // reply »
4 Comments

Crikey! You blogged about that!

http://www.bennadel.com/blog/2313-Experimenting-With-GitHub-Gist-Based-Code-Samples-For-My-Blog.htm


Apr 13, 2012 at 9:56 AM // reply »
1 Comments

Wow like it ...really great.........that's my aspiration to be a blogger but ........i haven't reached there as yet .........congratulations Ben and friends


Apr 16, 2012 at 6:59 PM // reply »
10 Comments

Ummm... I see the point, but only because there seems to be a flaw in the original design: Why create a single object responsible for sending dozens of different kinds of emails? What happens when I need to now send a shipping confirmation?

On the flip side, Apple frequently does something similar in Objective-C, when they create an interface file of selectors (methods) that only exposes the ones they want to be public.


Apr 17, 2012 at 4:01 AM // reply »
8 Comments

@Michael,

Because then all your mail and template handling code is in one place.
The CFC is probably a Facade or something that knows how to orchestrate the templates and CFMAIL - logic best not scattered all over the app.

Tom


Apr 17, 2012 at 9:33 AM // reply »
10 Comments

@Tom, I know it's a bit out of favor, but that's why you use inheritance.

If you have that much logic to manage, you create a master email cfc that knows how to do all of the things you mention, and then all of the individual kids inherit from it, perhaps overriding little things like the subject and body methods.

That approach is also better as it can help to eliminate the common logic you might have had in each individual mail method. Subclass with code that, say, knows how to iterate and display items in an order, and you have the parent for order confirmations, shipping notices, delayed or out of stock notices, and so on.

Put 'em all in a single "notifications" folder, so they can easily be found and edited, and you've solved your problem, old-school.


Post A Comment

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.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
May 17, 2013 at 7:42 PM
HashKeyCopier - An AngularJS Utility Class For Merging Cached And Live Data
Ben - thanks so much for posting these Angular articles and findings, they've been a huge help towards learning one of the more 'complex' JavaScript frameworks out there (IMO). I have been using Angu ... read »
May 16, 2013 at 5:01 PM
UPDATE: Parsing CSV Data Files In ColdFusion With csvToArray()
Your code was the closest thing I've found to obtaining some direction for converting ISO fields to values that CF can translate properly. Thank you for posting! ... read »
May 15, 2013 at 10:37 PM
Very Simple Pusher And ColdFusion Powered Chat
hi id making plz easy ... read »
May 15, 2013 at 6:07 PM
Making SOAP Web Service Requests With ColdFusion And CFHTTP
Ben, you once again saved my bacon at work. Thank you, thank you, thank you! ... read »
May 15, 2013 at 4:15 PM
What If All User Interface (UI) Data Came In Reports?
@Josh, Thanks! @Ben, I definitely recommend the David West book "Object Thinking" I've been quoting from. It goes deeply into the philosophy and history of OO programming. His breadth ... read »
May 15, 2013 at 11:36 AM
Ask Ben: Print Part Of A Web Page With jQuery
I found this helpfull when you need to keep (refresh) the original parent page after closing the iframe child print dialog (Hoping you're not using a form at this time so it won't submit again): On ... read »
May 14, 2013 at 7:13 PM
What If All User Interface (UI) Data Came In Reports?
@Jonah, If there's any books you'd recommend on the subject of domain modelling, I'd love to hear it. I just downloaded the free PDF of "Domain Driven Design Quickly". Figured I'd give it ... read »
May 14, 2013 at 6:57 PM
The UX Of Prototyping: Low-Fidelity Is The New High-Fidelity
@Phillip, I'm not sure I follow what you mean? Are you saying that you looked at the list of widgets provided by the jQuery UI and let that be your style guide? ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools