Exception-Driven Multi-Step Dependent Algorithms In ColdFusion

Posted April 11, 2008 at 8:49 AM by Ben Nadel

Tags: ColdFusion

Yesterday, I was discussing multi-step algorithms in which each step depended on the successful completion of all of the steps before it. I had put some thoughts down about some sort of ColdFusion custom tag system that would allow you to chain these kinds of logical steps together. In the comments to my post, Bradley Moore suggested that I use ColdFusion exceptions to handle the flow of the algorithm. To be honest, this thought had never even crossed my mind; I rarely use manually thrown exceptions - plus, I have heard a lot of people hate on the use of exceptions to do anything even slightly sneaky. I don't really know enough about the theory of it all to say whether or not exception usage is good or bad, so I figured I would try it out and see how it felt.

In this example, we are going to use a file upload page again. I like this kind of scenario because I feel that it is a very common one in the world of web-based business software, especially for any kind of application that allows users to upload data files that need to be parsed and processed in some way (think contact relations management, think CSV files, think bulk action modules). In the demo below, the algorithm uses 5 steps:

  1. Upload file
  2. Check file type (for CSV)
  3. Read in file data
  4. Process file data (sum all values)
  5. Direct user to confirmation page

That last step isn't really a part of the algorithm, but I listed it since it should only execute if steps 1-4 processed properly.

Ok, so here is my attempt to follow Bradley Moore's lead on exception-driven multi-step dependent algorithms:

  • <!--- Param form variables. --->
  • <cfparam name="FORM.file" type="string" default="" />
  •  
  • <!--- Create an array to hold the errors. --->
  • <cfset REQUEST.Errors = [] />
  •  
  •  
  • <!--- Check to see if the form was uploaded. --->
  • <cfif Len( FORM.file )>
  •  
  •  
  • <!---
  • Put a try/catch around the entire multi-step process.
  • Each sub-step will have the potential to throw an
  • exception to hault the algorithm from continuing.
  • --->
  • <cftry>
  •  
  • <!--- STEP 1: Upload file. ------------------------ --->
  • <cftry>
  •  
  • <cffile
  • action="upload"
  • filefield="file"
  • destination="#ExpandPath( './' )#"
  • nameconflict="makeunique"
  • />
  •  
  • <cfcatch>
  •  
  • <!--- Set error. --->
  • <cfset ArrayAppend(
  • REQUEST.Errors,
  • "There was a problem uploading the file."
  • ) />
  •  
  • <!--- Rethrow error to hault algorithm. --->
  • <cfrethrow />
  •  
  • </cfcatch>
  • </cftry>
  •  
  •  
  • <!--- STEP 2: Check file type. -------------------- --->
  • <cfif (ListLast( CFFILE.ServerFile, "." ) NEQ "csv")>
  •  
  • <!--- Set error. --->
  • <cfset ArrayAppend(
  • REQUEST.Errors,
  • "Only CSV files are allowed."
  • ) />
  •  
  • <!--- Throw error to hault algorithm. --->
  • <cfthrow
  • type="File.InvalidType"
  • message="Invalid file type"
  • detail="You have uploaded an invalid file type. Only CSV files are allowed."
  • />
  •  
  • </cfif>
  •  
  •  
  • <!--- STEP 3: Read in file. ----------------------- --->
  • <cftry>
  •  
  • <cffile
  • action="read"
  • file="#ExpandPath( './#CFFILE.ServerFile#' )#"
  • variable="strFileData"
  • />
  •  
  • <cfcatch>
  •  
  • <!--- Set error. --->
  • <cfset ArrayAppend(
  • REQUEST.Errors,
  • "There was a problem reading your file."
  • ) />
  •  
  • <!--- Rethrow error to hault algorithm. --->
  • <cfrethrow />
  •  
  • </cfcatch>
  • </cftry>
  •  
  •  
  • <!--- STEP 4: Process data. ----------------------- --->
  • <cftry>
  •  
  • <!--- Get initial sum. --->
  • <cfset flSum = 0.0 />
  •  
  • <!--- Loop over records in file. --->
  • <cfloop
  • index="strLine"
  • list="#strFileData#"
  • delimiters="#Chr( 13 )##Chr( 10 )#">
  •  
  • <!---
  • Do something with the data that might
  • throw an error; for example, interacting
  • with a database. For our example, we are
  • going to assume that each line contains a
  • NUMERIC value that we are going to add to
  • our sum.
  • --->
  • <cfset flSum += strLine />
  •  
  • </cfloop>
  •  
  • <cfcatch>
  •  
  • <!--- Set error. --->
  • <cfset ArrayAppend(
  • REQUEST.Errors,
  • "There was a problem processing the uploaded data. The value [#strLine#] could not be converted to a number. Please make sure that it is a valid CSV file."
  • ) />
  •  
  • <!--- Rethrow error to hault algorithm. --->
  • <cfrethrow />
  •  
  • </cfcatch>
  • </cftry>
  •  
  •  
  • <!---
  • ASSERT: If we have made it this far without any
  • errors being thrown, then our algorithm has completed
  • successfully. However, if any of the above steps
  • threw an error, this area will be bypassed and the
  • following CFCATCH will be executed.
  • --->
  •  
  •  
  • <!--- Redirect to confirmation page. --->
  • <cflocation
  • url="#CGI.script_name#?success"
  • addtoken="false"
  • />
  •  
  • <!--- ----------------------------------------- --->
  • <!--- Handle any errors that were thrown above. --->
  • <!--- ----------------------------------------- --->
  • <cfcatch>
  •  
  • <!---
  • One of the steps went wrong. Check to see if
  • we have an error message. If we don't then an
  • uncaught exception occurred.
  • --->
  • <cfif NOT ArrayLen( REQUEST.Errors )>
  •  
  • <!--- Set error. --->
  • <cfset ArrayAppend(
  • REQUEST.Errors,
  • "An unkown error has occurred (#CFCATCH.Message#)."
  • ) />
  •  
  • </cfif>
  •  
  •  
  • <!---
  • ASSERT: Since our algorithm didn't execute fully,
  • the server might have misc. garbage on it. Try to
  • clean up the system as best as we can.
  • --->
  •  
  •  
  • <!--- Try to delete uploaded file. --->
  • <cftry>
  •  
  • <cffile
  • action="delete"
  • file="#ExpandPath( './#CFFILE.ServerFile#' )#"
  • />
  •  
  • <cfcatch>
  • <!--- File could not be deleted. --->
  • </cfcatch>
  • </cftry>
  •  
  • </cfcatch>
  • </cftry>
  •  
  •  
  • </cfif>
  •  
  •  
  • <cfoutput>
  •  
  • <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  • <html>
  • <head>
  • <title>Dependent Algorithm Steps</title>
  • </head>
  • <body>
  •  
  • <h1>
  • Dependent Algorithm Steps
  • </h1>
  •  
  •  
  • <!--- Check to see if we have a success. --->
  • <cfif StructKeyExists( URL, "success" )>
  •  
  • <p>
  • <em>File upload was successful!</em>
  • </p>
  •  
  • </cfif>
  •  
  •  
  • <!--- Check to see if there were any errors. --->
  • <cfif ArrayLen( REQUEST.Errors )>
  •  
  • <h4>
  • Please review the following:
  • </h4>
  •  
  • <ul>
  • <cfloop
  • index="strError"
  • array="#REQUEST.Errors#">
  •  
  • <li>
  • #strError#
  • </li>
  •  
  • </cfloop>
  • </ul>
  •  
  • </cfif>
  •  
  •  
  • <form action="#CGI.script_name#" method="post" enctype="multipart/form-data">
  •  
  • <input type="file" name="file" size="60" /><br />
  • <br />
  •  
  • <input type="submit" value="Process File" />
  •  
  • </form>
  •  
  • </body>
  • </html>
  •  
  • </cfoutput>

As you can see, the entire algorithm is wrapped in a CFTry / CFCatch block. This will handle any uncaught exceptions as well as any exceptions explicitly thrown by the individual steps of the process. Then, each step of the algorithm is wrapped in its own CFTry / CFCatch block to handle low-level exceptions that might be generated. If any of the sub-steps generate an exception, an error is stored and the error is rethrown; this will prevent the "confirmation" code from executing inappropriately.

I have to say, I really liked this methodology. I love the fact that all the steps were on the same tab level (each step was tabbed in the same amount from the page gutter) making the algorithm very easy to follow from a visual standpoint. I know most people couldn't give to hoots about that, but that sort of stuff is hugely important to me. I also felt like the logic of the process was very easy to read and follow. Plus, I like that the parent CFCatch block provided a centralized place to clean up the server for any miscellaneous garbage that was produced as a side-product of the algorithm; this way, you are not muddling up the individual steps with sanitzation code - you are leaving all the steps intent-driven.

I know that a lot of people don't feel that exceptions should be used in this way; and I would tend to agree, not so much based on education, but more on gut feelings; however, after having written up this exercise, my thoughts on the topic are definitely changing. Something about it felt very good. Plus, after having read what Sean Corfield said about inexpensive exceptions in Java (ColdFusion), I have to say I am even more inclined to like this methodology.


You Might Also Be Interested In:



Reader Comments

Apr 11, 2008 at 11:22 AM // reply »
116 Comments

I'd look at the Chain of Responsibility pattern, since that is exactly what it is meant for. http://en.wikipedia.org/wiki/Chain-of-responsibility_pattern


Apr 11, 2008 at 11:57 AM // reply »
11,238 Comments

@Brian,

Someone suggested the Chain of Responsibility pattern to me the other day and it does look very interesting. My concern is that it is not aligned with the procedural nature of my code and therefore, I probably would not be able to leverage it in a good way.

For example, one step performs a file upload which results in a CFFILE variable existence which is utilized by subsequent steps. If the business logic was encapsulated in functions, how could I handle variable creation and usage across steps?

If you can give me some insight on that, I might be able to use this better.


Apr 11, 2008 at 12:26 PM // reply »
116 Comments

Couldn't you just pass the necessary data (whatever is needed out of the cffile variable) into the next step?

For fun, I added a CF version of the COR pattern to the Wikipedia article.


Apr 11, 2008 at 12:32 PM // reply »
11,238 Comments

@Brian,

I suppose you could. But, then, I would have to pass in the error array AND return it (since the array is passed by value). Which means, I then have to return the error array and the CFFILE variables. Which means I have to now return some struct that contains those values.

I guess I could come up with a common return structure, something like:

{
. . . Errors: Array,
. . . Data: Struct
}

But then, on top of that, I have to create the CFCs and put them in a meaningful place.

This just starts to feel like a lot of overhead for something that will be a one-off piece of functionality.

Do you use this pattern a lot? Have you felt the benefits of it?


Apr 11, 2008 at 5:19 PM // reply »
116 Comments

Yeah that's when you probably want to package up the necessary data into a common format as a CFC instance, that can be passed to each object in the chain.

I think you might be reaching about "having to" create CFCs and put them in the proper place. ;-) You know that would take all of 10 minutes.

I suppose it might be considered a lot of effort, though I think having it neatly encapsulated and ready for changes is a compelling point. In 6 months it would probably be easier and quicker to add a new item to the chain or to modify one item in the chain instead of having to deal with the large code block and the worry that a change in one spot breaks something further down.

I don't use it often but I have used it and found it helpful. Of course, there's no need to over-engineer things. But it is something to keep in mind. You might also keep an eye on performance, since throwing exceptions is a farily expensive operation.


Apr 11, 2008 at 6:08 PM // reply »
11,238 Comments

@Brian,

I don't want you to get the wrong idea - I love CFCs and I love trying to create objects that have most excellent encapsulation and reusability. My gripe here is that this is not really reusable - it's for a given form post. Yes, you could abstract it out into some generic chain for certain aspects and then maybe override it with CFC's that have form-specific actions, but that is probably gonna be over kill.

I am not sure that this is more usable / maintainable than the CFTry / CFCatch method. In the long run, they are both long files that have to be read; the difference is that one of the long files is inbetween CFComponent tags.

I want to like the CFC idea, because as I said, I really like CFCs in general, but I am just not sure I feel it for this situation; of course, if it wasn't useful, people wouldn't have created a "design pattern" for it.... just struggling to wrap my head around it.


Jun 24, 2009 at 11:17 AM // reply »
1 Comments

"hault" ???????????


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 21, 2013 at 7:46 PM
Using Plupload For Drag & Drop File Uploads In ColdFusion
No luck. At least I have uncovered the cause, URLScan 3.1. Here is what I see in the IIS log when a file is over 30mb. 2013-05-21 23:29:05 10.105.45.128 GET /plupload/assets/jquery/jquery-1.8. ... read »
May 21, 2013 at 6:12 PM
Using Plupload For Drag & Drop File Uploads In ColdFusion
Ben, I did not see you after Pete Freitag's Lockdown session at cfObjective but he said that IIS sets file size limits at 30MB by default which just happened to be the threshold for file size when ... read »
May 21, 2013 at 11:51 AM
Ask Ben: Parsing Very Large XML Documents In ColdFusion
Looking at my first ever XML document that I have to parse and put into MS SQL 2000 with CF8. I get it to list the desired Field name, many times over, and have a long list of this field name displa ... read »
May 21, 2013 at 9:25 AM
Turning Off and On Identity Column in SQL Server
you are awesome..i am lucky to get this blog between such a garbage one....Thanks, Prashant ... read »
May 20, 2013 at 4:38 PM
Using A Dynamic Column Name With ValueList() In ColdFusion
@Dana, Your confusion is well founded, since this is a very confusing features. In fact, it ONLY works if you use array notation. Meaning, that this: arrayToList( query[ "columnName" ] ) ... read »
May 20, 2013 at 4:34 PM
Using A Dynamic Column Name With ValueList() In ColdFusion
I was thinking chicken and the egg, I wouldn't have expected it to work in the valuelist going in I guess. Maybe I just need a beer, long day :) ... read »
May 20, 2013 at 4:29 PM
Using A Dynamic Column Name With ValueList() In ColdFusion
@Dana, That's if you're trying to reference a specific row. In this case, we're trying to reference the entire query column as one cohesive value. So, you are correct that if you wanted to output a ... read »
May 20, 2013 at 4:24 PM
Using A Dynamic Column Name With ValueList() In ColdFusion
I thought when you used array notation to reference queries you always had to have the row or it would throw a similar error as well? ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools