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 the jQuery Conference 2010 (Boston, MA) with:

Experimenting With A JavaScript Gateway To A Remote ColdFusion Persistence API

By Ben Nadel on

Last week, I was really trying to dig into modular JavaScript application architecture and development using RequireJS. In that exploration, I had an application that had Views and Controllers, but no real sense of a Model. To continue the exploration of JavaScript modularity, I wanted to play around with something that included a Model - one with a remote persistence API. As a first step in this direction, I created a simple ColdFusion API that stored contact information. Then, in order to interface with said remote ColdFusion API, I created a JavaScript Gateway module that encapsulated the access and mutation of the remote resources. I'm not yet sold on the use of a remote gateway; but, I thought it might be interesting to share.


 
 
 

 
  
 
 
 

I'm not going to go into detail about how the ColdFusion side of the client-server divide works; I don't think it's particularly relevant to the conversation. What you do need to know, however, is that the remote API (web service) always returns a "200 OK" status code (unless there is an unexpected server error); the success/failure of the request is embedded in the JSON (JavaScript Object Notation) payload that is returned from the API.

The response payload is always a hash with the following keys:

  • success (Boolean) - Flags success or failure of the API request.
  • data (Any) - The target information requested by the client.
  • code (Numeric) - Mirrors the "intended" HTTP status code.
  • error (String) - The error message sent back with non-200 codes.

The nature of this API response creates two points of interest in the JavaScript Gateway module: Since the data is always returned under an HTTP status code of 200, the gateway module needs to re-route "fail" responses to deferred rejection handlers. And, since the target data is encapsulated within an API response object, the gateway module also needs to know how to extract and pass-on only the relevant pieces of information to its deferred handlers.

Both of these tasks are accomplished using the pipe() method of the jQuery Deferred promise objects.

To keep things simple, I've only created the following four methods that map directly to the methods presented by the remote persistence web service:

  • deleteContactById()
  • getAllContacts()
  • getContactById()
  • saveContact()

Ok, let's take a look at the JavaScript gateway module. Keep in mind that this code is part of a larger RequireJS setup that I am not showing you.

contact-gateway.js - JavaScript Gateway Module For Contacts

  • // Define the Contact Gateway. This module provides access to the
  • // actual data storage of the contact information. This only knows
  • // to get access and mutate peristance records - it doesn't know
  • // anything else about anything.
  • define(
  • [
  • "jquery"
  • ],
  • function( $ ){
  •  
  •  
  • // I return an initialize component.
  • function ContactGateway( apiUrl ){
  •  
  • // Store the base API URL. This will be used to help
  • // construct all resource URLs.
  • this.apiUrl = apiUrl;
  •  
  • // Store the resource URL for a contact within the API.
  • // All access and mutation operations will be performed
  • // on this resource.
  • this.resourceUrl = (apiUrl + "index.cfm");
  •  
  • // Return this object reference.
  • return( this );
  •  
  • }
  •  
  •  
  • // Define the class methods.
  • ContactGateway.prototype = {
  •  
  •  
  • // I am simply a wrapper around the unerlying jQuery AJAX
  • // request that routes and unwrapps the respones payload.
  • accessAPI: function( options ){
  •  
  • // Access the API using the given options.
  • var response = $.ajax( options );
  •  
  • // Decorate the response so that the 200 OK reponses
  • // will be properly rejected IF the request is not
  • // truly a success.
  • response = this.addRoutingPipe( response );
  •  
  • // Decorate the response so that the data and errors
  • // can be extracted as they come throught.
  • response = this.addExtractionPipe( response );
  •  
  • // Return the decorated response promise.
  • return( response );
  •  
  • },
  •  
  •  
  • // I pipe the the response through an unwrapper that
  • // extracts the success data or the error message and
  • // passes it through to the promise chain.
  • addExtractionPipe: function( response ){
  •  
  • // Unwrap the response payload.
  • var unwrappedResponse = response.pipe(
  • function( response ){
  •  
  • // Return the succesful data.
  • return( response.data );
  •  
  • },
  • function( response ){
  •  
  • // Return the rejected error message.
  • return( response.code, response.error );
  •  
  • }
  • );
  •  
  • // Return the piped response.
  • return( unwrappedResponse );
  •  
  • },
  •  
  •  
  • // I pipe the given AJAX request through a switch that
  • // checks for a SUCCESS flag. Since all requests come
  • // back as 200 OK responses, we need to use the success
  • // flag to route the value through the appropriate
  • // resolve / reject pipeline.
  • addRoutingPipe: function( response ){
  •  
  • // Pipe the response through our router.
  • var routedResponse = response.pipe(
  • function( response ){
  •  
  • // The HTTP response came back; check to see
  • // if the API request was a success.
  • if (response.success){
  •  
  • // Simply pass this through to the
  • // currently resolved response.
  • return( response );
  •  
  • }
  •  
  • // The API was a FAILURE. Let's create a new
  • // deferred object and reject it.
  • return(
  • $.Deferred().reject( response )
  • );
  •  
  • },
  • function( response ){
  •  
  • // The API request came back with an unexpected
  • // error (ex. HTTP failure, no internet, 500).
  • // Return a normalized failed response through
  • // to the currently rejected response.
  • return({
  • success: false,
  • data: "",
  • code: 500,
  • error: ("Unexpected error: " + response.status + " " + response.statusText + ".")
  • });
  •  
  • }
  • );
  •  
  • // Return the routed response.
  • return( routedResponse );
  •  
  • },
  •  
  •  
  • // I delete the contact with the given ID.
  • deleteContactById: function( id ){
  •  
  • // Delete the given contact from the API.
  • var response = this.accessAPI({
  • type: "post",
  • url: (this.resourceUrl + "?action=delete"),
  • data: {
  • id: id
  • },
  • dataType: "json"
  • });
  •  
  • // Return the response promise.
  • return( response );
  •  
  • },
  •  
  •  
  • // I get all the contacts.
  • getAllContacts: function(){
  •  
  • // Get all the contacts from the API.
  • var response = this.accessAPI({
  • type: "get",
  • url: (this.resourceUrl + "?action=getAll"),
  • data: {},
  • dataType: "json"
  • });
  •  
  • // Return the response promise.
  • return( response );
  •  
  • },
  •  
  •  
  • // I get the contact with the given ID.
  • getContactById: function( id ){
  •  
  • // Get the contacts from the API.
  • var response = this.accessAPI({
  • type: "get",
  • url: (this.resourceUrl + "?action=get"),
  • data: {
  • id: id
  • },
  • dataType: "json"
  • });
  •  
  • // Return the response promise.
  • return( response );
  •  
  • },
  •  
  •  
  • // I save the given contact information (performing
  • // either an insert or an update based on ID).
  • saveContact: function( id, name, email ){
  •  
  • // Insert or Update the given contact on the API.
  • var response = this.accessAPI({
  • type: "post",
  • url: (this.resourceUrl + "?action=save"),
  • data: {
  • id: (id || 0),
  • name: name,
  • email: email
  • },
  • dataType: "json"
  • });
  •  
  • // Return the response promise.
  • return( response );
  •  
  • }
  •  
  •  
  • };
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // Return the gateway constructor.
  • return( ContactGateway );
  •  
  •  
  • }
  • );

In the above code, each access and mutation method makes use of the accessAPI() method. This method leverages the underlying jQuery ajax() method which returns a promise(). The AJAX promise() is then piped into two filters:

  • addRoutingPipe()
  • addExtractionPipe()

The addRoutingPipe() method makes sure that non-200 response codes gets "rejected" rather than "resolved" in the final promise. The addExtractionPipe() method makes sure that the "data" key is returned on resolved requests and that the "error" and "code" keys are returned on rejected requests. This gateway and these pipes allow any calling code to become oblivious to and decoupled from the transport mechanism used to sent and receive data to and from the remote persistence web service.

I know that libraries like Backbone.js and Spine.js build the concept of a gateway directly into the Model. I'm not ready to do that just yet since my grasp on the architecture is not very strong. I am sure, however, that as I get more into this, I'll start to understand how concepts can be merged and reduced.

I'm not completely sold on this yet, since I'm just getting into it; but, I'm comfortable with where it's going. Next, I'll probably try to create a Contact and ContactService modules. The Contact module will represent a contact record. The ContactService module will get data from the ContactGateway and use it to populate Contact instances... I think.




Reader Comments

Ben,

Question, what is the reason for you moving away from using CF for client interaction? I am interested to know your direction. Many of your post as of late indicate (to me) you are only using CF for the back end processing of data.

Cheers,

Michael

Reply to this Comment

I recently noticed that when ColdFusion errors it natively returns a server-error: true header, so I started looking for that on the client in ajax responses and setting it on the server when I cfcatch an error. It has made debugging remote cfc calls a bit easier.

Reply to this Comment

I do this kind of thing all the time with cold fusion and extjs. I wrote a stack for it's Ext Direct API to integrate with coldfusion that allows both on demand cfc instantiation to call it's methods, as well as access persistent cfcs, and threw a little bit of security integegration into it.

Reply to this Comment

@Michael,

I still use ColdFusion for all my back-end stuff. And, in reality, I am still using ColdFusion for the majority of my front-end templating (ie. using ColdFusion Markup to generate HTML. The level of thick-client application at this point is more theory than it is practice (in my own world). But, I'm looking for ways to minimize the client-server interaction to make the user experience more snappy and responsive.

I wouldn't necessarily use this for a more "website"-esque site. This would be more for a real "application"-eqeue interface (ex. Gmail).

@Bob,

That's really interesting! I've never noticed that before. I'll have to check that out. Thanks for the heads up :D

@Jim,

Sounds very cool. I think that is the kind of direction I want to move in more JavaScript-heavy app stuff; as @Michael was saying, using the ColdFusion server for more of an API than a rendering engine.

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.