Skip to main content
Ben Nadel at CFUNITED 2009 (Lansdowne, VA) with: Brice Green
Ben Nadel at CFUNITED 2009 (Lansdowne, VA) with: Brice Green

Considering The "Bounded Context" Of Error Messages In A ColdFusion Application

By
Published in Comments (7)

Error handling in a web application is a deceptively hard concept. I've been building ColdFusion applications for two-decades, and I'm only just now starting to feel like I'm finding helpful patterns that balance complexity and utility. And, I still have so much to figure out. As I've been refactoring / modernizing the code for my ColdFusion blog, I keep running in to unanswered question. My blog has both a public facing system and an admin facing system; and, I'm starting to wonder if these are two distinct bounded contexts for errors and error messages.

ASIDE: As I've been refactoring my ColdFusion blog, I've been slowly cleaning up the code and trying out different organizational and control-flow patterns. Here are a few articles that are highly relevant to this conversation:

My refactoring has, to date, focused almost entirely on the public facing portion of this blog. That is, the portion that the "users" interact with. As such, all of the error messages have been about "first person" data. That is, "Your" data. So, if a user goes to leave a comment and the name field is empty, the error message reads:

Please enter your name.

Now, in the admin facing portion of this blog, I have the ability to edit comments as you'd expect. And, what I'd like is for both the public and the admin facing portions of the site to use the same domain model. That is, I want to share the layer of the "application core" that determines that a "name" can't be empty and still be valid.

The problem is, if an admin (Me) goes to save a comment without a name, I want the error message to read:

Please enter the author's name.

Unlike in the public facing portion of the blog, where I'm talking to the user, within the admin I'm talking about the user. As such, the error messages should be different.

In my first post about centralizing error handling on this blog, my plan would have been to catch-and-wrap errors in the Workflow layer. Meaning, within the Admin facing workflows, I'd catch the "empty name" error and then throw a new error specific to the Admin-facing workflow. This error could then be handled in the centralized error handler and be given a unique error message.

Catching-and-wrapping as a rule turned out to be overly tedious and syntactically verbose. I was wrapping things just for the sake of making error messages more flexible. But, now I'm wondering if it even makes sense for the public facing and admin facing applications to share the same error handling? If the two applications had completely separate error boundaries, then I could map the same domain error onto two different error messages.

NOTE: I'm not against catching-and-wrapping errors when it makes sense. I'm just no longer convinced that it makes sense as often as I had originally thought it did.

Meaning, in the public facing error handling ColdFusion component, I could have:

component {

	public struct function getResponse( required any error ) {

		// ... heavily truncated snippet ...

		switch ( error.type ) {
			case "BenNadel.Entity.Member.Name.Empty":
				return(
					as422({
						type: error.type,
						message: "Please enter your name."
					})
				);
			break;
		}

	}

}

And, in the admin facing error handling ColdFusion component, I could have:

component {

	public struct function getResponse( required any error ) {

		// ... heavily truncated snippet ...

		switch ( error.type ) {
			case "BenNadel.Entity.Member.Name.Empty":
				return(
					as422({
						type: error.type,
						message: "Please enter the author's name."
					})
				);
			break;
		}

	}

}

Note that the same error.type value is being mapped onto two different error messages.

Once I let my mind start to think about having two distinct error handling services, other things started to make more sense. For example, the routing within the two different applications is completely different. Also, one system has session management, the other does not. What this means is that the two different error handling services will change for different reasons. Which is another "smell" that they should be two different "things".

I've only just begun to refactor my admin. So, we'll see how this thinking plays out in the code. I'm also concerned that my desire to share any code between the public and admin facing portions is ill-fated. After all, it's that what a "bounded context" is? A separation of the definition of what things are? That said, I do think there is value to having some fundamental truths about the data be shared across the two systems.

Option: Just Make Error Messages More Generic

One possible solution to my problem could be to just make all of the error messages more generic. So, instead of saying:

Please enter your name.

... I could use something more abstract:

Please enter a name.

... or, I could make it more passive:

Name cannot be empty.

But, this doesn't feel like a solution that is moving in the correct direction. Error messages should be helpful. And, this means that they will likely become more detailed over time (as users express confusion over the current messages). As such, a race to make them more generic feels antithetical to the very mission of the error message.

Want to use code from this post? Check out the license.

Reader Comments

4 Comments

My first immediate thought, comparing those two last code samples, is to use a "dictionary". I can't think of the pattern offhand, maybe "resource bundles". Your value of "BenNadel.Entity.Member.Name.Empty" is already the key, but in this scenario, you load the right RB file based on the context (front vs. admin). That makes it super-fast to edit, and in fact you could even get fancy and have missing keys (meaning new keys you've added to the code but not created the text for yet) write to a new (pending) file so you can just copy them out to the new file, set the values, and not have that data committed to the repo until you include it to the official RB files.

What resource bundles aren't as good at is when you need to handle "0 pies", "1 pie", "2 pies" stuff, but I've seen solutions to those, they just require more code. And don't get me started on the variations of that by language.

15,902 Comments

@Will,

Oh, very interesting idea. I've never used a resource bundle (typically used for internationalization I think); but, I always considered the centralized error handling as something that would facilitate that kind of concept.

One drawback of the resource bundle approach, in theory, is that it becomes harder to make the error response messages dynamic. Going back to your pies example, any kind of information that is provided in the Error object itself that might be used to generate a non-static error message becomes challenging.

Let me have a noodle on this. Definitely wroth a think.

9 Comments

You mentioned that one side has session management, the other doesn't. How about utilize that in your component? Use one error handler and depending on if a session variable is passed in (you know it's the admin side) and change the latter part of the error message accordingly. I use client variables (client.usergroupid) to be specific to tell me what type of a user is involved.
Just a thought 🙂

15,902 Comments

@Jaana,

As I've been noodling on this stuff, one of the thoughts I had was kind of along those lines actually. I had considered defining different messages in my error configuration. So, something like this:

switch ( error.type ) {
	case "BenNadel.Entity.Member.Name.Empty":
		return(
			as422({
				type: error.type,
				// TWO different error messages.....
				message: "Please enter your name.",
				adminMessage: "Please enter the author's name."
			})
		);
	break;
}

And then, I could use .message by default; or, .adminMessage depending on the context, like you are saying. Will definitely let that simmer in the back of my mind.

15,902 Comments

@Will,

Oh, it is just a private method on that component that sets some default values for the error response with status code 422:

/**
* I generate a 422 response object for the given error attributes.
*/
private struct function as422( struct errorAttributes = {} ) {

	return( getGeneric422Response().append( errorAttributes ) );

}

/**
* I return the generic 422 Unprocessable Entity response.
*/
public struct function getGeneric422Response() {

	return({
		statusCode: 422,
		statusText: "Unprocessable Entity",
		type: "UnprocessableEntity",
		title: "Unprocessable Entity",
		message: "Sorry, please validate the information in your request and try submitting it again."
	});

}

So, basically you just pass-in the attributes you want to override.

Post A Comment — I'd Love To Hear From You!

Post a Comment

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel