Dec 19 2007

Model-Glue "Results" vs. Other Frameworks

Posted by Joe Rinehart at 12:09 PM
7 comments
- Categories: Model-Glue

One of the questions I frequently answer about Model-Glue is why there's the added complication of the "results" mechanism. In other frameworks, such as Mach-ii or ColdBox, the controller tier often explicitly adds another event to the event queue.

Update: Mach-ii Correction

Mach-ii has a feature named "event mappings" that solve the issue I bring up in this post, decoupling controller code (listeners) from their context in terms of other event names. Thanks to Sean Corfield for pointing this out.

Framework-neutral pseudo-code:

<cfset event.addAnotherEvent("someOtherEventName") />

For a more concrete example (read close, we'll show why Results help using this one!):

<!--- If the user submitted their profile form w/ valid input, show the saved message event --->
<cfif profileFormIsValid()>
   <cfset event.addAnotherEvent("showUserSavedMessage") />
<!--- Else, show the form again --->
<cfelse>
   <cfset event.addAnotherEvent("showProfileForm") />
</cfif>

It's simple, and it's direct. Nobody has a problem understanding it, and it does exactly what is says.

Quickly on with Model-Glue (and with some prodding from Sean Corfield), I saw that this method has a serious flaw. In a Front Controller framework (MG, M2, ColdBox, Fusebox...), the controller tier becomes brittle when it's too aware of its execution context.

Too aware of execution context?

Experienced MVC and Model-Glue developers know that the best controller is a thin controller. It doesn't overstep its bounds to perform business logic or know too much about the view (other than form / url names), other controller, or anything else going on "outside" of itself.

When we start hard-coding event names into a controller's listener function, we begin violating this. The controller's listener functions go from lean bits of control logic and take on the role of flow-control, knowing their context.

When does this get ugly?

Let's pretend that we've got two forms, both editing a user's profile. One is used by the user her/himself, and the other is used by an administrator. When they're both submitted, the same unit of work should be performed: values from the form are collected, validated, and handed off to the model for action.

We want to follow the DRY principle, and basically use the same listener function / server-side action to handle either.

In our non-result setup, we might end up with something like this:

<!--- If the user submitted their profile form w/ valid input, show the saved message event --->
<cfif profileFormIsValid()>
   <cfif getCurrentEvent() eq "showProfileForm">
      <cfset event.addAnotherEvent("showUserSavedMessage") />
   <cfelseif getCurrentEvent() eq "adminProfileForm">
      <cfset event.addAnotherEvent("showAdminUserSavedMessage") />
   <cfelse>
      <cfthrow message="Invalid event to save a user!" />
   </cfif>
<!--- Else, show the form again --->
<cfelse>
   <cfif getCurrentEvent() eq "showProfileForm">
      <cfset event.addAnotherEvent("showProfileForm") />
   <cfelseif getCurrentEvent() eq "adminProfileForm">
      <cfset event.addAnotherEvent("adminProfileForm") />
   <cfelse>
      <cfthrow message="Invalid event to save a user!" />
   </cfif>
</cfif>

Whoa Nelly! In order to re-use code, we just had to explode it to handle multiple string literals that are all based on stuff going on outside of the controller itself! That's not good!

Implicit Invocation to the rescue!

Mach-ii and Model-Glue are all about implicit invocation. Both focus on a broadcast / listener scheme (with differing implementations). Model-Glue, however, adds a second II implementation, in terms of its "results" mechanism. When a message listener (controller function) runs, it can "state" that something has happened, such as an invalid form submission. It's up to the outside world (your applications Model-Glue XML file) to determine whether or not to handle it.

Under this mechanism, we can simplify our code drastically:

<cfif profileFormIsValid()>
   <cfset event.addResult("formDataIsValid") />
<cfelse>
   <cfset event.addResult("invalidFormData") />
</cfif>

Now, multiple contexts (the admin and the user profile forms) can execute the same code, each taking their own action based on what's occurred.

We've:

  • Kept the Controller lean. All it does is take state, hand it to model components, set view data, and announce results.
  • Decoupled Controller code from View context. We're no longer dependent on what event is being run when inside our controller.
  • Increased flexibility and maintenance. We can now add more events that save profiles (such as a remote event or a JSON exposure of the logic) without having to account for it in a massive .

Parting thoughts

One benefit of architectural frameworks should be provision of easy to use mechanisms that increase the quality of an application's architecture. While it's straightforward to implement an MVC framework, MVC alone doesn't provide the same degree of flexibility and elegance as MVC plus Implicit Invocation.

Given that the MVC frameworks for ColdFusion all organize your code into "units of work" in the form of Fuses, message listeners, or "event handler functions" (best way I can describe ColdBox), ask yourself this: If your units of work call other "units of work" by an explicit name, how's your architecture any different from building an application based on the brittleness of <cfmodule> and <cfinclude>, just like we did ten years ago?

Comments

Jason

Jason wrote on 12/19/07 1:30 PM

Joe,

Great post.

What are the pros/cons of taking even this amount of decision tree out of the controller? Does it make the most sense for my controller to even care about the &amp;lt;cfif&amp;gt; condition?

If my profileFormIsValid() method returns the expected result, especially something generic like 'formDataIsValid' or 'invalidFormData', instead of a Boolean, is that even tighter?

I.e.:

&amp;lt;cfset event.addResult(profileFormIsValid()) /&amp;gt;

Or what about even adding the result within the model itself, since the model is already taking in and returning the Event?
sal

sal wrote on 12/19/07 8:19 PM

mmh. interesting. I understand, although do you really think the users (profile form) and the administrators (profile form) be handled from the same controller? I would assume that they'd be in different controllers purely for separation of concern...? I usually have my 'admin' controller handling my administrative tasks...

any thoughts?
Tony Garcia

Tony Garcia wrote on 12/20/07 5:39 PM

So basically -- you're claim is that if you use any other framework other than Model-Glue you might as well be coding with cfmodule and cfinclude, &quot;just like we did ten years ago&quot;? Those are some strong words...
Sean Corfield

Sean Corfield wrote on 12/22/07 6:28 PM

It's worth pointing out that Mach-II has event mappings specifically to support the exact same approach as Model-Glue: you announce generic &quot;success&quot; or &quot;failure&quot; events and then in the event-handler XML you specify mappings to the actual event names. In other words, Mach-II and Model-Glue are equivalent in this respect - it's just that the *default* for each is different (Mach-II defaults to raw event names, Model-Glue defaults to using mappings).

It's also worth pointing out that Mach-II 1.6 introduces the same publish/subscribe message/event mapping that Model-Glue has had all along.
Joe Rinehart

Joe Rinehart wrote on 01/02/08 8:46 AM

@Sean - Thanks for updating me with the Mach-II bits. Have event mappings always been in Mach-II? I haven't seen them used, but I haven't used the framework in a long, long time.

@Tony - Not quite what I intended. What I wanted people to ask themselves was what the difference is between explicitly routing through events with magic string versus using cfmodule/cfinclude style code. It's not just Model-Glue that decouples this: from what I know, it's the original intent of Fusebox! Units of work should be encapsulated and then arranged, arbitrarily, through an external mechanism, rather than explicitly strung together internally.
Sean Corfield

Sean Corfield wrote on 01/02/08 6:13 PM

@Joe, I don't remember exactly when event-mapping was introduced but it was pretty early on. I was certainly using it at Macromedia back in the 1.0.x days (and I just confirmed it is listed in the 1.0 DTD).

And, yes, Fusebox enabled a decoupling of fuseaction names (used in links / forms) and filenames / directory structures as an early part of its evolution although it doesn't have the &quot;double indirection&quot; that mappings and broadcasts offer. Maybe I'll add that in 5.6? :)
Matthew

Matthew wrote on 09/29/08 4:33 AM

It's also worth pointing out that Mach-II 1.6 introduces the same publish/subscribe message/event mapping that Model-Glue has had all along.
http://www.batteryfast.co.uk/hp/dd880.htm hp dd880 battery,
http://www.batteryfast.co.uk/hp/315338-001.htm hp 315338-001 battery,
http://www.batteryfast.co.uk/ibm/02k6620.htm ibm 02k6620 battery,
http://www.batteryfast.co.uk/ibm/thinkpad-t20.htm ibm thinkpad t20 battery,
http://www.batteryfast.co.uk/ibm/02k7072.htm ibm 02k7072 battery,
http://www.batteryfast.co.uk/ibm/thinkpad-t30.htm ibm thinkpad t30 battery,
http://www.batteryfast.co.uk/ibm/thinkpad-r50.htm ibm thinkpad r50 battery,
http://www.batteryfast.co.uk/ibm/thinkpad-t40.htm ibm thinkpad t40 battery,
http://www.batteryfast.co.uk/ibm/08k8192.htm ibm 08k8192 battery,

http://www.batteryfast.co.uk/ibm/thinkpad-x40.htm ibm thinkpad x40 battery,
http://www.batteryfast.co.uk/ibm/thinkpad-x60.htm ibm thinkpad x60 battery,
http://www.batteryfast.co.uk/ibm/thinkpad-r32.htm ibm thinkpad r32 battery,
http://www.batteryfast.co.uk/ibm/thinkpad-r40.htm ibm thinkpad r40 battery,
http://www.batteryfast.co.uk/ibm/thinkpad-r40e.htm ibm thinkpad r40e battery,
http://www.batteryfast.co.uk/ibm/thinkpad-t60.htm ibm thinkpad t60 battery,
http://www.batteryfast.co.uk/gateway/s62044l.htm gateway s62044l battery,
http://www.batteryfast.co.uk/gateway/m320.htm gateway m320 battery,
http://www.batteryfast.co.uk/gateway/m325.htm gateway m325 battery,

http://www.batteryfast.co.uk/gateway/4000.htm gateway 4000 battery,
http://www.batteryfast.co.uk/gateway/solo-1400.htm gateway solo 1400 battery,
http://www.batteryfast.co.uk/gateway/solo-1450.htm gateway solo 1450 battery,
http://www.batteryfast.co.uk/gateway/4ur1865of-2-qc-oa2.htm gateway 4ur1865of-2-qc-oa2 battery,
http://www.batteryfast.co.uk/gateway/400.htm gateway 400 battery,
http://www.batteryfast.co.uk/gateway/450.htm gateway 450 battery,
http://www.batteryfast.co.uk/gateway/bat0016.htm gateway bat0016 battery,
http://www.batteryfast.co.uk/gateway/m1200.htm gateway m1200 battery,
http://www.batteryfast.co.uk/gateway/m1300.htm gateway m1300 battery,

Write your comment



(it will not be displayed)