A Fusebox Implementation of the Model-View-Controller Design Pattern

Design Patterns

At its core, most software is about simulating or modeling something that exists in the real world. This "something" may be physical objects such as documents or machines; it may also be more abstract notions such as workflow or billing. Software should let us create a scale model of these real-world things so that we can more easily work with them.

Unfortunately, this sounds a good deal simpler than it is. Users often want to manipulate the model in certain ways ("We want to be able to either generate invoices from the system or have our sales people create them by hand.") only to be told, "You can't do that!" The reason they "can't do that" is because the software's interface-the forms, pages, and reports that are used with it-is wed so tightly to the underlying scale model that changing one will break the other.

Because this is so pervasive a problem, many different attempts have been made to solve it. Over time, a few "best practice" approaches often evolve from many experiments for a specific type of problem. The term, design pattern, refers to these best practice approaches.

Model View Controller

One software design pattern that has been widely adopted in Object Oriented circles was invented by the visionaries at Xerox's Palo Alto Research Center (PARC). Known as model-view-controller, or simply MVC, the modeling of the external world, and the visual feedback to the user are explicitly separated and handled by three types of object, each specialized for its task.

The model is responsible for representing the external world in an abstracted or idealized state. Algorithms and databases belong to the model.

The view is responsible for displaying information from the user and retrieving information from the user. In the web world, this is normally done using HTML, with Flash and Java used when greater flexibility is desired.

The controller interprets mouse and keyboard inputs from the user, commanding the model and/or the view to change as appropriate.

Such an approach-separate components with distinct responsbilities working together-works very well, and MVC (or an adaptation of it) is the dominant paradigm behind many software architectures.

A Fusebox Implementation

To illustrate how you might adopt MVC for Fusebox, let's first look at a non-MVC approach to handling functions related to users. In this approach, I'll create a single circuit and call it User. What kinds of fuseactions should this circuit handle? Here's a partial list of fuseactions that might fit into such a circuit:

addUser
editUser
deleteUser
displayUser
listUsers
insertUser
updateUser
removeUser

You'll notice that some of the fuseactions are very similar (addUser and insertUser, for example.) One of these fuseactions (addUser) will display a form for adding new user information, while the other one (insertUser) will process the action, inserting the user into a database, LDAP server, or some other data storage mechanism.

It seems natural that since all of these fuseactions are related to managing users, we would let them all be handled by the User circuit. H.L. Mencken once said, " For every complex problem there is an answer that is clear, simple--and wrong." In fact, there are two distinct types of fuseactions at work here. The first category is integral to the notion of a user. Such integral fuseactions include requests to remove a user, update the information on a user, or return a list of all users. The second category, though, is really about an interface--a particular way of interacting--with the User circuit.

A "thought experiment" can make this clearer. Imagine that you decide to have the fuseaction, addUser, handled by the User circuit. That seems reasonable enough. As you explore the business process behind adding a user, you discover that while a guest can fill in a form, that user must still be approved and a customer service representative assigned to the user.

Knowing this, you write a dsp_NewUserForm.cfm and place it in User. You add this to the User FBX_Switch.cfm:

<cfcase value="addUser">
  <cfset XFA.submitForm="User.insertUser">
  <cfset XFA.cancelForm="Home.main">
  <cfinclude template="dsp_NewUserForm.cfm">
</cfcase>

Now, let's say a guest fills out a form and clicks the SUBMIT button. With the XFA.submitForm variable set, the form will return to the fusebox with a fuseaction of User.insertUser. Remember, though, that a user must first be approved and then a CSR must be assigned before the user is accepted. So, our FBX_Switch file might have this in it:

<cfcase value="insertUser">
  <cfinclude template="-

Hmmm…we have a problem. We need to get approval on the user, and for that, a custom tag in the Admin circuit must be run. So, perhaps, XFA.submitForm should have been set to Admin.validateUser. But then, we would need to go over to the CSR circuit and request the assignCSR fuseaction before finally finishing up back in User.acceptUser, so perhaps it should point to CSR?

Something is wrong here. We can see that the choice of User.insertUser for the initial form's XFA.submitForm seems arbitrary. It might just as well have gone to Admin or even CSR. But worse, this would mean that the Admin circuit would have to know about the CSR circuit and some circuit-either Admin or CSR-would have had to know about the User circuit. All these dependencies-circuits needing to know of other circuits-is at variance with a central Fusebox principle of maintaining circuits as separate and self-contained, whenever possible.

The model-view-controller design pattern offers a way out of this murkiness. Its advice is to think of processes as belonging to either model or view or controller, and to keep these all distinct and separate. To begin, I set up "abstract circuits" for Model and View. Abstract circuits are not meant to handle fuseactions directly (and so do not need any FBX_Switch.cfm file). Abstract circuits only contain other circuits that will respond to fuseactions. Here's a example setup:

Now, let's try our thought experiment again. A guest clicks a link to register:

<a href="#self#?fuseaction=#XFA.register#">Register now!</a>. XFA.register has been set to Guest.addUser. Guest is a circuit in the View section. Here is the snippet from the FBX_Switch.cfm for the fuseaction, addUser.

<cfcase value="addUser">
  <cfset XFA.submitForm="Controller.addUser">
  <cfinclude template="dsp_NewUserForm.cfm">
</cfcase>

When the user submits this form, it will take them to Controller.addUser. We still need to do the same things in response to a new user request. Notice how we do them now, though:

<cfcase value="addUser">
  <cfmodule
    template="#fusebox.rootpath##self#"     fuseaction="Admin.validateUser"
    attributecollection="#attributes#">
  <cfif isApproved>
    <cfmodule
      template="#fusebox.rootpath##self#       fuseaction="CSR.assignCSR"
      attributecollection="#attributes#">
  </cfif>
  <cfmodule
    template="#fusebox.rootpath##self#
    fuseaction="User.acceptUser"
    attributecollection="#attributes#">
</cfcase>

In the Fusebox MVC pattern, the controller circuits are responsible for the interfaces to View and Model circuits. Note that no fuses are called from the Controller circuit. Instead, fuseactions in other circuits that call fuses are called.

This approach is very helpful in dealing with the inevitable changes that occur over the lifetime of an application. In the first scenario I laid forth, guests provided information to make themselves users. They then had to be approved and assigned a CSR. But suppose an administrator wants to create a user. This is a different scenario.

Instead of presenting the same dsp_NewUserForm.cfm that a new user would see, the administrator may have a different form-one that allows them to assign a CSR at the same time that a user is added. Certainly the look and feel of the view function will be quite different.

In the first scenario, our work flow led from the new user form to Admin.validateUser, followed by CSR.assignCSR and finally, User.acceptUser. Since the administrator is creating the new user, we can simplify the process, eliminating the need for a separate user validation and CSR assignment. We just need to call the User.acceptUser fuseaction.

If we were to mix view and model functions together, we would find that User.addUser would no longer work. We would need something like User.addUserByGuest and User.addUserByAdmin. That will work, certainly, but it is fragile.

Suppose that a large change in requirements occurs that does not fit into the existing structure at all. For example, it might be that the client decides that guests should be able to begin the process of adding themselves by sending an email that has their information in a particular format. We can create a User.addUserByGuestByEmail, but surely this is becoming a kludge.

Using MVC, though, we can easily let both the administrator and guest have their own interface into the model by sub-circuits of Administrator and Guest off of the View circuit, each with their own addUser fuseaction. If, later, we must add another addUser-for a supervisor, perhaps-we do not need to make changes to the view and model code, but will create separate fuseactions and separate fuses (perhaps even a separate view sub-circuit) to accommodate these new requirements.

After you work with MVC for a bit, you'll see that the Controller circuit(s) are quite different from either Model or View. While both model and view are often excellent candidates for code reuse from one application to another, the controller code is typically very specific to a single application-or even to a single context within the same application. I have found that Fusebox is an ideal architecture to adopt the model-view-controller design pattern, and that doing so simplifies coding and aids code reuse and maintainability.