Twitter: raymondcamden


Address: Lafayette, LA, USA

Some thoughts on working with CFCs remotely

07-08-2011 8,949 views Flex, ColdFusion 31 Comments

Last night my boss pinged me with a few questions concerning ColdFusion Components (CFCs) and Flex. His questions led to a long discussion about working with remote CFCs (and to be clear, this would apply to Flex or Ajax) and what some of the best/recommended practices are. He also asked for a demo of what I was talking about so I whipped up some code this morning to illustrate the concepts. If any of this blog entry does not make sense, I'll be moderating an open chat room later today (4PM CST) on Connect:

https://experts.adobeconnect.com/cfjedimaster

Hop on in and we can talk about this blog entry, or anything else. Anyway, on with the discussion.

Let's begin with an example of a web site that includes CFCs meant to be called remotely. As I mentioned earlier, my boss was using Flex but in our case, I'm doing to everything with simple HTTP requests. No Flex, no Ajax, just all in the browser. Version 1 of my application has the following structure:

Forgive me for including those two "Copy of" files, pretend they don't exist. You can see I've got an Application.cfc, a model folder, and two CFCs. Let's begin by looking at art.cfc:

view plain print about
1component {
2    writelog(file="application",text="art.cfc created");
3
4    remote string function helloWorld(string name="Nameless") {
5        return "Hello, #arguments.name#";
6    }
7
8}

Nothing too crazy here. The CFC has one method, helloWorld. But note I included a writelog in the constructor area. What's the constructor area? Any code in a CFC that is not in a method will be executed whenever a CFC is created. Any code. That means code on top, bottom, and even code hiding between methods. Typically you would never put code outside of methods like that, but if you do, try to put it on top.

So the question is - what happens when I use this CFC? If I put this in my browser: http://localhost/remotecfcexample/v1/art.cfc?method=helloworld&returnformat=json I'll get the response ("Hello, Nameless") but I'll also get a line in my Application.log file. If I rerun this URL a few times, I'll get multiple log entries.

What this implies is that every time a remote client (again, it could be Flex, AJAX, mobile app using Flex or AJAX, simple HTTP request, magical unicorns) a new CFC instance is created. This becomes more important as we begin to add additional CFCs and make them use each other. This is actually what spurred my boss's initial question. How - from art.cfc, could he make use of util.cfc? Let's first look at util.cfc:

view plain print about
1component {
2    writelog(file="application",text="util.cfc created");
3
4    public function simpleBold(required string s) {
5        return "<b>" & arguments.s & "</b>";
6    }
7
8    public function simpleItalics(required string s) {
9        return "<i>" & arguments.s & "</i>";
10    }
11    
12}

This CFC is - surprise surprise - a utility component. It's got a function for bolding and italicizing text. Very fancy. So how would art.cfc make use of it? In order to do so it needs to create an instance. For example:

view plain print about
1component {
2    writelog(file="application",text="art.cfc created");
3
4    remote string function helloWorld(string name="Nameless") {
5        var util = new util();
6        return util.simpleBold("Hello, #arguments.name#");
7    }
8
9}

Nothing terribly complex here. I just made an instance of util in my method and then used it to format my text. But let's look at our log file now after a few requests.

I've got four lines there representing two separate requests. Each and every time I execute my call to helloWorld, I've got not one but two requests going on. This is probably not going to matter for 99% of web applications. ColdFusion can create CFCs fast. Really fast. But obviously we could be leading ourselves down a scary path if we start creating more complex components.

So what did I recommend?

First, I suggested he put his core CFCs into the Application scope. This would allow him to create his CFCs one time only. It also allows him to use an initialization method to create an instance of util. The assumption being that more methods in the art.cfc will probably need it. Let's look at art.cfc first:

view plain print about
1component {
2    writelog(file="application",text="art.cfc created");
3
4    public function init() {
5        variables.util= new util();
6    }
7
8    public string function helloWorld(string name="Nameless") {
9        return variables.util.simpleBold("Hello, #arguments.name#");
10    }
11
12}

This version has two important changes. First note the use of the init method. ColdFusion will run this automatically when the CFC is created. I store the util component in the CFC's Variables scope. Then later on in my method, I can run the utility CFC's function. util.cfc didn't change at all. Let's look at my Application.cfc:

view plain print about
1component {
2
3    this.name="remotecfcdemo_v2";
4    this.sessionManagement="false";
5    
6    public boolean function onApplicationStart() {
7        application.art = new model.art();
8        return true;
9    }
10
11}

So this Application.cfc is rather simple, but you can see now that I use the onApplicationStart method to create an instance of my art component in the application scope. Done. Except... now the question is - how in the heck do we call that component method?? You can't run a CFC stored in a variable remotely. (Although see my notes below!) This is where a new CFC comes in. I'm going to create a "service" CFC that simply acts as a proxy to my Application components. Here is my application's new structure:

You can see my new folder, remote, with a component called artservice.cfc. I should point out that the names here are purely arbitrary. I named them "nicely" of course, but there is noting special about my folder being called remote. Let's look at the CFC now.

view plain print about
1component {
2    writelog(file="application",text="artservice.cfc created");
3
4    remote function getHelloWorld(string name="Nameless") {
5        var result = application.art.helloWorld(arguments.name);
6        return result;
7    }
8
9}

The component has one method, getHelloWorld, that wraps a call to the Application-scope art component. The method could do a bit more, additional logging, security, etc., but in this case it simply wraps the cached call. Notice too I've got my writelog here for testing. So now let's do a few hits and see what gets recorded.

On the very first hit, the one at 10:34:00, you can see art, util, and artservice are created. But notice the last entry. When I reloaded, only the artservice component was recreated. My core CFCs, the meat of my application, did not need to be recreated. This gives us some additional benefits as well. We can move our components out of the web root to be extra sure they are protected from unauthorized access. By creating a core "doorway" we can perform all kinds of other operations like I mentioned above - logging is a great one.

So that's the gist of it. I've got a few notes I want to share as well, but consider what follows as extra credit. I've included the source for this as an attachment to this blog entry.

  • Experienced ColdFusion users will probably want me to bring up ColdSpring. ColdSpring is an excellent tool for managing CFCs and relationships, and can actually create remote proxies automatically. That's hot! But as I told my boss - baby steps.
  • I mentioned above that when I instantiated art.cfc in the Application scope, it meant that folks could not run the CFC anymore. That's not 100% accurate. I still had the file under web root. Someone could run it. Now I did change the method access from remote to public, but what if I hadn't? If I ran the method it would try to make use of the Variables scoped util component created in init. But the init function is not run for remote requests! Therefore an error would occur. In general I'm a big believer in keeping everything possible out of web root. Only expose what you truly have to.

Download attached file

31 Comments

  • Commented on 07-08-2011 at 11:18 AM
    Hopefully this blog entry will spur a good bit of discussion. I always enjoy reading how others make use of remote service calls from Flex/AJAX back to CF.

    I just started to make use of cflogin and roles to enable remote access only to logged in users via an AIR client and am trying to find some best practices for that.
  • MikeG #
    Commented on 07-08-2011 at 11:44 AM
    When did CF start running the init method automatically? How do you pass arguments if that happens? In the past when creating instances I have always
    session.myInstance=createobject(....).init(args);

    Next question, why not just use the extends argument for the cfcomponent tag? Seems to me if you reference a persistent scope directly in a cfc you may have problems. My practice has always been "CFC functions can only use data/objects that are passed into them as arguments or available to them though variables or this scope". Granted this is beyond "baby steps"; but it is as difficult to unlearn a practice as it is to learn one.. By following this self imposed rule, I can use cfcs across applications with or without a framework to prop them up.
  • Commented on 07-08-2011 at 11:45 AM
    I generally use an AjaxProxy.cfc which functions like your ArtService.cfc. I would think a better name might be ArtProxy.cfc for your example.

    The reason being that files named *Service.cfc are generally assumed to be Service Objects. As such, they should be singletons created in the application scope just as your other two CFCs were.
  • Dave DuPlantis #
    Commented on 07-08-2011 at 12:05 PM
    Baby steps are important, because you have at least two advantages once you get beyond them.

    One is that if you're having trouble using a framework, you already understand the basics, so you've got a point of reference: you just need to figure out how Framework X implements whatever you're doing.

    Another is that when you get to something that's not easily done within the scope of your framework, you'll be more comfortable expanding it than if you didn't understand how it did the basic stuff in the first place.

    I like the tip about executing code on creation of a CFC ... as you say, not that I'd really want to use it that much, but I'm sure the need will come up from time to time.
  • Devin #
    Commented on 07-08-2011 at 12:18 PM
    I understand the purpose of this blog entry, but what I found confusing was that out of all 3 of the CFCs, you chose the one that was most likely to have instance data as the one to instantiate in the application scope, and the two that were most likely to not have instance data (singletons) were not instantiated in the application scope.

    I know it's just an example, but in the real world, you'd most likely cache your services/utils in the app scope and not the entities / model objects.
  • Commented on 07-08-2011 at 1:03 PM
    Lots to reply to...

    @MikeG: I believe CF9. And yes, you can pass args. Given an init func that accepts a name arg I can do:

    <cfset t = new test("Ray")>

    To your question - why not use extends? In general, extends is meant to be used for inheritance. Ie, A is a B. In our case, our component wasn't extending util, it was needing. Hence why I made an instance. To be clear, yes, if you did extends it would work, but it's not considereded best practices, and immediately fails when your CFC needs another one as well.

    @Adrian: No, I checked. My name is right. Yours is wrong. End of story. ;) Yeah - it could get confusing if use service components in your model. Again though I'm trying to KISS here.

    @Devin:

    Well, its an example, really. And I disagree - the service compnent here was specifically built for remote calls, so it can't be cached. If you mean "service" as in what Adrian brought up, then sure, but that's not how I used it.
  • Commented on 07-08-2011 at 1:29 PM
    I have a question for everyone:

    How does everyone handle returning errors back from their remote service calls?

    e.g. I have a remote service that accepts a customer and saves or updates it. However, if the e-mail address address already exists (for a new user), I want to return an error message back to my AIR app that states that that e-mail already exists. Currently I'm just returning back the "Customer" object. Would the "best" method be to add an "error" object to my Customer that I would check to see if any errors had been thrown, or create another object that has error and customer properties, and return that back. For both methods I would probably check in the resultHandler of the client app for the error and display it to the end user.

    I tried using CFThrow, then catching that error in the AIR app, but I found that if I didn't have robust debugging turned on, it sends back a generic error message (found a bug mentioned for CF 9.0.1 in the Adobe bug tracker, hopefully 9.0.2 will have a fix for it).
  • Commented on 07-08-2011 at 1:32 PM
    @Gareth: I won't pretend to have the best answer but I'll give my opinion. But before that, let me address your last point.

    902 (who says the next version is 902? ;) will not "fix" this as it isn't broken. Robust Exception info is only for debugging. "Rich" error handling is ok CFM to CFM (or I should say, local code on server to local code on server), but when a remote guy invokes your call, the result error should not contain info that can be used as an attack vector. If it did, I'd just force an error remotely and use it to help attack your site.

    So I'd probably do this:

    a) For CFCs in your model, go ahead and throw real errors, that's what they are there for.

    b) For your Remote CFC (proxy, service, whatever), I'd return a struct that contains information about the request. This could include status flags, like 0 == error with a message detailing why.
  • Commented on 07-08-2011 at 1:34 PM
    @Gareth
    I generally return a status (0 or 1 depending on success) and an error array on every request. If status is 0 (failed) it is handled by the front-end dumps out the error array and handles them accordingly.
  • MikeG #
    Commented on 07-08-2011 at 1:40 PM
    for remote cfc calls I rarely return just the data; typically a struct with 3 keys, status, msg, data. In the cfc function, lots of catch blocks to return well formed messages for expected problems and one generic catch that sets "Error encountered" message. Only give remote callers error messages that you craft, try to to let them have system error messages.
  • Commented on 07-08-2011 at 1:48 PM
    @Ray,
    It does actually appear to be a bug though
    http://cfbugs.adobe.com/cfbugreport/flexbugui/cfbu...
    I try to throw back just a message, but get a generic message "Unable to invoke CFC - Event handler exception."

    @all,
    Yeah, I'm definitely getting to see the advantages to something more constructive when returning data back to my calling app. Just didn't want to have to return back a generic "data" property back to my app, but I guess it doesn't matter as I have to type it when it returns back to my client app anyway :) No different than "event.result" now I think about it.
    Thanks!
  • Commented on 07-08-2011 at 1:52 PM
    Woah - that's a very different thing though. You didn't mention the use of onCFCRequest. You only said (afaik) that you threw an error in the CFC method. Your bug then is in error handling via onCFCRequest. Looks like a real bug to me.
  • Commented on 07-08-2011 at 2:03 PM
    Yeah, it was definitely a bit of a pain. I threw the message back, then it gets gobbled up and converted to the generic message. Would be fantastic if it worked correctly :) And it did for all my local testing as I had robust error handling turned on. I like idea of passing back more info to my calling page though...seems like a good solution.
  • Devin #
    Commented on 07-08-2011 at 2:14 PM
    Remote services can certainly be cached, when using the onCFCRequest().

    And yeah, that error handling is a tricky thing. It makes it impossible to catch the real error in your Flex fault handlers.

    One thing I've done (which is a PITA) is to return some sort of error formatted data and utilize the availble hooks in the RemoteObject/Message classes that allow you to intercept the RPC results (before your RPC responders receive it), check to see if it's an error, and then produce a FaultEvent and pass manually pass it to your fault responders.

    The idea is that once they fix the error serialization in CF (assuming they do), you can just delete your interceptor code in the flex app and be done.
  • Commented on 07-08-2011 at 2:17 PM
    @Devin: I'm sorry - but how? Are you saying you intercept the call in onCFCRequest and 'replace' the CFC with one from the app scope? I don't believe that would work. You can stop the request I suppose and redirect it. Is that what you mean?
  • Commented on 07-08-2011 at 2:17 PM
    @Devin,
    Ah, I was scouring through the ResultEvent debug result looking for that exact thing...do you have any code or where I can look for that?
    Thanks.
  • Devin #
    Commented on 07-08-2011 at 2:30 PM
    @Ray: It's no different than using the onRequest handler to intercept a request but include a different file. With CFCs, you're just intercepting the request and calling a different CFC instance (the one stored in the application scope).

    @Gareth: Take a look in the docs at the RemoteObject.convertResultHandler() method. That should get you started. If you need further assistance with it, feel free to email me at devin@shinynet.com
  • Commented on 07-08-2011 at 2:44 PM
    I wonder if it runs before the remote method check. Ie, if I called

    /remote/goo.cfc?method=X

    and X wasn't even valid, would onCFCRequest still run.
  • Devin #
    Commented on 07-08-2011 at 3:32 PM
    Good question. I can't say that I've tried, but in theory it should... considering one of the primary purposes of onCFCRequest is to intercept the request and do [whatever you want] including not even calling the intended method.

    That would allow you to call dynamic methods such as:
    getProductsByColorAndSizeAndCategory("red", "XL", "shirt") and use the methodname and args parameters to determine the real request.
  • Commented on 07-08-2011 at 3:33 PM
    Of course, onMissingMethod might work for that too. I always forget if it works remotely though.

    2 things for us to test. Race ya... ;)
  • Commented on 07-08-2011 at 3:36 PM
    Ok I tested onMissingMethod. If access=remote, it can work fine remotely. (Kinda obvious I guess, but still...)
  • Devin #
    Commented on 07-08-2011 at 3:59 PM
    I tested it with Flex, calling a fake method with fake arguments and dumpint the arguments scope from the onCFCRequest to a file and it worked with no problems.

    For shits and grins, I attempted calling a non-existent CFC and that did not work. So I guess only the CFC is checked before onCFCRequest.
  • Commented on 07-08-2011 at 4:06 PM
    Did you try onMissingTemplate?
  • Devin #
    Commented on 07-08-2011 at 4:15 PM
    I could not get that to work. But I've never used onMissingTemplate before so it's possible I'm doing something wrong. I simply set it to return true hoping the request then be sent to the onCFCRequest.
  • Michael #
    Commented on 07-11-2011 at 9:31 AM
    @Devin - was it a CF error when calling the non-existent CFC or a web server error?
  • Brian W. #
    Commented on 07-12-2011 at 11:47 AM
    Ray, I can't tell you how helpful this is! I use quite a few remote calls in many of my applications that leverage fairly status CFC's that could now be put into the application scope.

    One question I have is how would your example impact server memory? I know you're not big on "one-rule-of-thumb to rule them all" but could you give a do and don't scenario to help with what should be considered before using this technique?
  • Commented on 07-12-2011 at 11:51 AM
    It shouldn't have any real impact. I think if you imagine 5-10 or so CFCs for a normal app, all a few hundred lines, you are probably taking 10-20K of ram. To be honest, I'm just guessing here. I've never seen it be an issue. It's easy enough to test though with the built in Server Monitor. You can quickly see how much RAM your own application is taking.
  • Brian W. #
    Commented on 07-12-2011 at 11:56 AM
    Thanks Ray - I'm going to have to do some playing, i mean, testing... I'm probably a little too excited about this but this post really puts my head into a different place when thinking about remote calls (I use my remote calls for Ajax w/ JSON).

    Thanks again!
  • Commented on 07-12-2011 at 12:00 PM
    <cfsoapbox>
    To me, the sign of any good technology is how excited it makes you. If you see it demonstrated and immediately start thinking about all the cool crap you could build, then it's a good tech. If you see it and think, my god, I'd lose half my hair just writing Hello World, then that's a tech to avoid.
    </cf
    soapbox>
  • Dave DuPlantis #
    Commented on 07-12-2011 at 12:03 PM
    I think I just made a connection between working with Lisp and VB 6 and needing to wear a hat when I go outside these days.
  • Mary Jo #
    Commented on 07-22-2011 at 9:01 AM
    Ah yes, that bug with onCFCRequest caused me many, many headaches! You'll see I put my vote in on it, and I hope others will as well, because Adobe consider it not very important since you can work around it. The workaround though can be very, very time consuming! I'm using Mate in my Flex app, and it had really nice Fault Handling, but the bug totally broke that as it doesn't return useful information. I ended up having to roll my own return object that would have a success indicator, and then either the return data, or an error struct. Then I had to extend Mate to look for this in all the return objects and handle it accordingly. Of course, I'm also sending any errors to my site-wide error handler on the server that logs everything and notifies me of the error.

    As for my CF backend, I did find that the more complicated my model got, the more problematic it got. I'm using CF9 ORM, and it's hard to really control how much data gets returned. I ended up building a remote facade layer that includes assembly methods. So it would take the ORM objects, and create much simplier DTOs (data transfer objects) to send to Flex. A similar re-assembly method was used for return data. I also including caching methods so any DTO could be cached in EHCache based on its last modified date, so I only would run assembly if the object was not yet in cache, or had been changed since last cached. All this was a bit of work, but gives us total control over the data sent back and forth to Flex, and gave us huge performance increases as well by decreasing the massive objects CF was trying to send.

Post Reply

Please refrain from posting large blocks of code as a comment. Use Pastebin or Gists instead. Text wrapped in asterisks (*) will be bold and text wrapped in underscores (_) will be italicized.

Leave this field empty