Model-Glue "Results" vs. Other Frameworks
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:
For a more concrete example (read close, we'll show why Results help using this one!):
<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:
<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:
<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?
7 comments - Posted by Joe Rinehart at 12:09 PM - Categories: Model-Glue



