Skip to main content
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Jeremiah Lee
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Jeremiah Lee ( @JeremiahLee )

OOPhoto: Further Exploration Of The Facade And Controller-Model Interaction

By on
Tags:

The latest OOPhoto application can be experienced here.

The latest OOPhoto code can be seen here.

In my previous OOPhoto step, I added a Facade layer to my application. I use this Facade to create a Named-Argument driven interface with which the Controller can interact. This has gotten some side-ways looks from a few people, but nothing that really concerned me. But then, on Tuesday night, I was having dinner with Peter Bell and he expressed some confusion over my Facade implementation.... and, when Peter's confused, I'm concerned. I realized that maybe I am not explaining myself very well. And so, I wanted to put this demo together to help explore my decision a bit better.

I don't want to talk about the Facade layer itself - the Facade is just a means to separate out like-minded methods. Instead, I want to talk more about the passing of data between the Controller and the Model. When I started talking about FORM processing, some people suggested that I just pass the FORM scope off to the Model for processing. My instinct has always been to condemn this suggestion. Something inside of me feels very strongly that the Model should not be depending on the implementation of the View; after all, isn't that why we have MVC (Model-View-Controller) - so that we can theoretically have many interfaces for a single application?

For some reason, this seems to be an conversation that people have trouble visualizing, so I think it will be most effective to look at some code. Below, I have created a single page that mimics the processing of FORM data via the direct passage of the FORM scope to the "Model":

<!--- This is our mock "serivice layer" method. --->
<cffunction
	name="ProcessForm"
	access="public"
	returntype="void"
	output="false"
	hint="I am a method used to mock the processing of form data.">

	<cfdump var="#ARGUMENTS#" />
	<cfabort />

	<!--- Return out. --->
	<cfreturn />
</cffunction>


<!--- Param form data. --->
<cfparam name="FORM.from" type="string" default="" />
<cfparam name="FORM.to" type="string" default="" />
<cfparam name="FORM.submitted" type="boolean" default="false" />


<!--- Check to see if form was submitted. --->
<cfif FORM.submitted>

	<!--- Process the form data. --->
	<cfset ProcessForm( FORM ) />

</cfif>


<cfoutput>

	<!--- Reset the buffer and stream content. --->
	<cfcontent type="text/html" reset="true" />

	<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
	<html>
	<head>
		<title>Add Crush</title>
	</head>
	<body>

		<h1>
			Add Crush
		</h1>

		<p>
			Let us know who likes who!
		</p>

		<form action="#CGI.script_name#" method="post">

			<!--- Flag form submission. --->
			<input
				type="hidden"
				name="submitted"
				value="true"
				/>

			<p>
				<!--- This person. --->
				<input
					type="text"
					name="from"
					value="#FORM.from#"
					/>

				<em>likes</em>

				<!--- That person. --->
				<input
					type="text"
					name="to"
					value="#FORM.to#"
					/>

				<input type="submit" value="Submit" />
			</p>

		</form>

	</body>
	</html>

</cfoutput>

As you can see, the Controller portion of the page does almost no work - it simply takes the FORM scope and hands it off to the Model (our mock function) for processing:

<!--- Check to see if form was submitted. --->
<cfif FORM.submitted>

	<!--- Process the form data. --->
	<cfset ProcessForm( FORM ) />

</cfif>

When we submit the above form, we get output that looks like this:

FORM Data Passed Through With Standardized Names.

This is all well and good in the above example. But, as Nassim Taleb would say, let's perform a "thought experiment." Let's pretend that this form is on a public website and that this form is getting hit by thousands of spam bots a day. Ads for Viagra and Barely Legal Web Cams are coming in left and right. To prevent this, we have to take some immediate action. We decide that we are going to give our form fields completely random names and rely solely on the order of the submitted fields. This is not a cure-all, but heck, it might just slow the bots down a little bit.

Our quick thinking developers jump in and update the code:

<!--- This is our mock "serivice layer" method. --->
<cffunction
	name="ProcessForm"
	access="public"
	returntype="void"
	output="false"
	hint="I am a method used to mock the processing of form data.">

	<cfdump var="#ARGUMENTS#" />
	<cfabort />

	<!--- Return out. --->
	<cfreturn />
</cffunction>


<!---
	We have been getting TONS of spam submissions on this form,
	so we are going to start randomly naming the form fields to
	try and slow down the bots.

	As such, it is the numbering of the form fields that will
	become important, not the names.
--->
<cfset arrForm = [] />

<!---
	Loop over the submitted form fields to populate the form
	array values. We are relying on the fact that the FieldNames
	property of the FORM is always submitted in the same order.

	If this is the first run of the page, we have to param the
	field names.
--->
<cfparam name="FORM.FieldNames" type="string" default="" />

<!--- Loop over field names. --->
<cfloop
	index="strFieldName"
	list="#FORM.FieldNames#"
	delimiters=",">

	<!--- Add the next form field to our array. --->
	<cfset ArrayAppend(
		arrForm,
		FORM[ strFieldName ]
		) />

</cfloop>


<!--- Param form arra data. --->
<!--- From: 2. --->
<cfparam name="arrForm[ 2 ]" type="string" default="" />
<!--- To: 3. --->
<cfparam name="arrForm[ 3 ]" type="string" default="" />
<!--- Submitted: 1. --->
<cfparam name="arrForm[ 1 ]" type="boolean" default="false" />


<!--- Check to see if form was submitted. --->
<cfif arrForm[ 1 ]>

	<!--- Process the form data. --->
	<cfset ProcessForm( FORM ) />

</cfif>


<cfoutput>

	<!--- Reset the buffer and stream content. --->
	<cfcontent type="text/html" reset="true" />

	<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
	<html>
	<head>
		<title>Add Crush w/ Anti-Spam</title>
	</head>
	<body>

		<h1>
			Add Crush w/ Anti-Spam
		</h1>

		<p>
			Let us know who likes who!
		</p>

		<form action="#CGI.script_name#" method="post">

			<!--- Flag form submission. --->
			<input
				type="hidden"
				name="#CreateUUID()#"
				value="true"
				/>

			<p>
				<!--- This person. --->
				<input
					type="text"
					name="#CreateUUID()#"
					value="#arrForm[ 2 ]#"
					/>

				<em>likes</em>

				<!--- That person. --->
				<input
					type="text"
					name="#CreateUUID()#"
					value="#arrForm[ 3 ]#"
					/>

				<input type="submit" value="Submit" />
			</p>

		</form>

	</body>
	</html>

</cfoutput>

As you can see from the new example, our form field names are now UUIDs. We've had to update some of the FORM paraming to get around this, but not too much. Notice that we are still passing the FORM struct directly to the "Model":

<!--- Check to see if form was submitted. --->
<cfif arrForm[ 1 ]>

	<!--- Process the form data. --->
	<cfset ProcessForm( FORM ) />

</cfif>

This causes a HUGE problem. Because our View needed to change the way it was built, our Model is now completely unprepared to deal with form processing. Rather than getting a struct with names like "From" and "To", it gets names like 7C52EC41-1372-2659-DE29762FDE8EF227 and 7C52EC60-1372-2659-DECEA332D12CD035. Furthermore, these form names are unique to every page request:

FORM Data Passed Through With Non-Standardized Names.

So, how do we fix this. We have a few options:

We can update the way our Model processes form data across the entire site.

Unfortunately (or rather fortunately), this is the only form on the site that has spam issues. If we change the way the Model processes data (to reply on ordered fields rather than named fields), the other 99% of the forms might start breaking.

We can update the way the Model processes just this form.

Yeah, we can do that.... but this is the way the anti-spam is working now; we might need to change it soon. Do we really want to be changing our Model every time that our anti-spam techniques change? Plus, what about other Views that use this Model? What if we have a similar form that is behind a secured login such that it doesn't get affected by spam bots. If we change the way the Model works, we would have to go back and change any existing Views that touch this Model.

We can change the way our Controller passes data to the Model.

What if, rather than passing the FORM struct, we passed in simple form values. Let's update the way the "Controller" portion of the page interacts with the "Model" to submit the individual fields:

<!--- Check to see if form was submitted. --->
<cfif arrForm[ 1 ]>

	<!---
		Process the form data. This time, rather than passing
		off the entire FORM data struct, we are going to let
		the "Controller" translate the View fields into the
		expected Model fields.
	--->
	<cfset ProcessForm(
		From = arrForm[ 2 ],
		To = arrForm[ 3 ]
		) />

</cfif>

Here, we are letting the Controller translate the View naming conventions of our anti-spam form to the expected properties of the form processing method. Now, when we run the code, we get the following output:

FORM Data Values Passed Through Using Controller-Based Translation.

Are you beginning to see the potential here? By creating a value-based API to the Model rather than a struct-based API, we are allowing the View and the Controller to adapt without having to touch the Model layer at all. And, because this branch of our Controller is specific to this View, we can change it as much as we want without any concern for existing Views that might make use of the same Model.

I hope that this demonstration has shed a bit more light on my design decisions. Of course, this discussion is just concerned with the data transportation, not the idea of a Facade layer; these processing methods are in my Facade, but maybe they are in your Service layer - the location of them is secondary - the implementation of their API is our current concern.

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

Reader Comments

24 Comments

Ben,

It seems to me that you're not fond of the idea of just passing in the form structure to the facade as an argument collection. My question is why not? I think it's VERY important that the controller be able to map form values to arguments in the facade as needed (as you demonstrated in your post above), but if you're form field names match up with your argument names, why not just pass in the form struct as an argument collection? The facade already defines the arguments it will be operating on, so it doesn't matter if you pass in more arguments than are defined. In fact, I actually think it would be preferable to pass the form collection through as an argument collection as much as possible. That way if you add a field later on, there's one less place to have to worry about making sure you update so that your form fields get sent to the model. As I mentioned before, I think it's VERY important that the controller be able to have complete control on how data submitted from the UI gets mapped to the arguments passed in to the facade, but shouldn't custom mapping of fields be the exception rather than the norm? Basically a "reasonable defaults" approach to data marshaling? That's part of why I used Brian Kotek's formUtils in the proof of concept I whipped up a while back. It makes it SOOO easy to pass a complex form, such as one with a hierarchy, into your model. Mapping values to arguments just seems like busy work to me. Although there are TOTALLY valid cases for that, but again, that should be the exception rather than the norm. Also, the things is your controller has to know how to map fields/values to the arguments in the facade. So no matter what your UI/Form and controller are tightly coupled. So no matter what your controller has to be aware of the names of the form fields your form is submitting, and how to map those fields to the argument list. So if your controller has to be aware of this already and it just happens that the form fields are the same names as the argument names, then you may as well pass things in as an argument collection.

Just wanted to share why I think passing the form scope through as an argument collection can be a very convenient thing. Hope things are moving along with your OO development project.

Love the series, keep it up,

- Kurt Bonnet

15,640 Comments

@Kurt,

I think your point is very valid - if your form fields line up with your model names, then yes, absolutely, it makes sense to pass the FORM scope in as the ArgumentCollection to the service method. Of course, just so we are on the same page, I am talking only about the ArgumentCollection and not about passing the FORM scope in as a single argument, ex:

DoSomething( ArgumentCollection = FORM )

...vs....

DoSomething( FORM )

I would go only with the former and not with the latter. The form allows for the flexibility of both argument based and struct-based parameter passing.

As far as this not being the norm - that it should be the exception to the norm - I don't necessarily agree. That depends on your naming conventions. For me, it would be the norm as my form naming conventions (ex. first_name) are different than my model naming conventions (ex. FirstName). But, the beauty of the ArgumentCollection, as you state, is that it can be leveraged nicely in both ways.

1 Comments

Happened upon your post while browsing waiting for college football to start. My 2 cents....

For simple apps doing the mapping or passing the form structs is basically the same thing, just a preference. Might want to read Eric Evans book on Domain Drive Design then checkout MachII and/or ModelGlue MVC frameworks for ColdFusion. Basic idea is your form info is converted into a CFC object (that you define) and then passed to your controller for processing. That way your working more type specific code, which is just better for larger apps (but I'm in that camp). Anway, I'm out.

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