ColdFusion Developer's Journal Special "Frameworks" Focus Issue: Mach-II

It's all Simon's fault.

We say this to all framework writers who, even now, are trying to recover from the task assigned them by CFDJ's editor-in-chief: provide an article and an implementation of the Macromedia Pet Market application in their chosen framework.

Realizing that our first sentence might not serve as sufficient explanation for those weary framework authors, allow us to provide further clarification...

It began with an innocent-seeming dinner for speakers at the recent "Fusebox and Frameworks" conference. When we found ourselves seated next to Simon Horwith, we had a great deal to talk about. During our conversation, Simon suggested that it would be very helpful to ColdFusion programmers if a reference application existed that was implemented in several different frameworks. "We could have a cfpetmarket.com," he said, "and have different framework authors write a reference application and provide an article to go along with it. For the next issue of CFDJ."

This was the point where we went wrong - badly wrong - and the reason for which an apology is due: we agreed with Simon. Given the short time frame, our indiscretion meant that we were guaranteeing that our fellow framework authors (and ourselves) were in for some late nights. For our hasty improvidence, we offer a corporate mea culpa.

Coming Up with a Plan
The three of us put our heads together. It seemed the first thing to do was to review the existing application. This presented us with a problem: frankly, we didn't like what we saw. Installation of this simple application was anything but simple. The application almost seemed to be illustrating "worst practices" including such miscues as:

In short, we didn't much like it. But what to do? Although it was tempting to rewrite the application from the ground up, retaining only the look and feel of the existing one, we decided that in order to focus on the ease of use of Mach-II and to help developers who are often tasked to retrofit existing applications, we would strive to keep as much of the code as possible as it was originally written.

Our decisions also led us not to integrate any of the helper frameworks such as ColdSpring or Tartan, something we surely would have done had we written the code from the ground up. In short, the code in the Mach-II version of the Pet Market should not be viewed as exemplary, but rather as the result of retrofitting an existing application - warts and all - to make use of Mach-II.

Mach-II Components
Specifically, we wanted to show how nicely Mach-II can fit into applications in its role in the Model-View-Controller (MVC) architecture. MVC is a well-accepted architectural design pattern in which the components of an application are separated by functionality.

Model components represent the business logic of the application as well as a software simulation of the inventory of the business domain (and are often called domain model components). Model components might include representations of orders, customers, products, etc.

View components form the user interface, allowing users to receive information from the application and make requests of the application. HTML pages, Flash movies, and Java applets are all examples of view components.

Mediating between model and view are controller components, receiving requests and calling on model and/or view components as needed to fulfill a request. For example, a user request to edit application preferences would entail a call to the model to determine the existing preferences and a call to the view to display a form for changing these preferences.

Many applications use some form of controller, even if nothing more than a switch statement. Frameworks such as Mach-II, Fusebox, and Model-Glue provide a more extensive controller and often include prebuilt functionality as well as "hooks" for developers who want to extend the capabilities of the framework.

Within the Mach-II world, various controller functions are assigned to different Mach-II elements. The central element in a Mach-II application is an event. An event is an all-purpose mechanism for encapsulating a request and the results (if any) of fulfilling that request. In our "edit preferences" example, Mach-II creates an event object (an instance of Mach-II's Event class) that comes with prebuilt functionality.

Among other things, events can store information related to a request. In the "edit preferences" example, Mach-II stores the results of asking the model for the user's current preferences (possibly a query) in the event. (Event objects have arguments that can be accessed with prebuilt get/setArg methods.)

When the view page - in this case a user preference form - is called, it gets any information it needs from the event (shown in the illustration as a cube). In a Mach-II application, as in any well-designed MVC application, the model and the view components exist completely separate and independent of each other. Such loose coupling, as it is called, aids in both code reuse and maintainability.

The rest of Mach-II's machinery exists to support the Event. A Listener element is a CFC that extends Mach-II's Listener class and "listens" for events, taking appropriate action for the individual event. Using our example of a user editing their preferences again, we might have a UserListener that is notified of an "editPreferences" event. It is the UserListener that actually calls the model for the user's existing preferences.

Listeners do not, though, call view pages. Doing so would decrease the listener's ability to be reused and make application maintenance more difficult. Instead, Mach-II uses a declarative programming mechanism in which the instructions for responding to a particular event are written not in code (programatically) but in some sort of simple configuration file (declaratively).

For this configuration file, Mach-II uses the file, mach-ii.xml. Different sections of the configuration file allow the developer to customize how Mach-II works for any particular application. (Rather than forcing the developer to adapt to Mach-II, Mach-II adapts to the developer.)

Mach-II's Configuration File
If you haven't done so, download the M2PetMarket application from www.mach-II.com. A subdirectory, controller, holds the mach-ii.xml file. The file has six sections:

  1. properties: Used to set general application settings including such properties as applicationRoot (the directory the application files are to be found in) and defaultEvent (the event to be used when none is specified).
  2. listeners: Used to register any listeners used in the application. Mach-II allows you to use a single, general purpose listener or to specify separate listeners dedicated to specific areas of interest (e.g., UserListener, ShoppingCartListener, etc.).
  3. event-filters: Used to preprocess events before any listeners respond to them. Typical functionality found in event-filters would include granular security ("Can this user call this event?") and server-side form validation.
  4. plugins: Used to register components that are called at various "plugin points" throughout the life cycle of an event. The Mach-II Pet Market application uses a plug-in to ensure, among other things, that a user is logged into the application.
  5. event-handlers: Used to indicate how this Mach-II application should respond to a specific event. In our "edit preferences" example, the code for the editPreferences event-handler would look like this:

    <event-handler name="editPreferences">
        <notify listener="UserListener" method="getCurrentPreferences"
        resultArg="userPreferences" />
        <view-page name="userPreferenceForm" />
    </event-handler>

  6. page-views: Used to register the view pages used within this application. Pages that are registered here (providing their physical location) can then be referenced within an event-handler by (as shown in our example).

Examining the M2PetMarket
There is, of course, a DTD available for the mach-ii.xml file, but the set of elements and sub-elements is fairly simple. Let's look at the individual elements in the configuration file for the M2PetMarket application. We start with properties.

<properties>
    <!-- Standard Mach-II application settings. -->
    <property name="applicationRoot" value="/M2PetMarket" />
    <property name="defaultEvent" value="home" />
    <property name="eventParameter" value="event" />
    <property name="parameterPrecedence" value="form" />
    <property name="maxEvents" value="10" />
    <property name="exceptionEvent" value="exceptionEvent" />
    <!-- PetMarket application settings. -->
    <property name="dsn" value="M2PetMarket" />
    <property name="locale" value="en_US" />
</properties>

The applicationRoot is set to /M2PetMarket, indicating that a directory, M2PetMarket, should be a subdirectory of your Web root. The defaultEvent is home. When users initially come to the site, this is the entry point to the application. We won't change the properties, eventParameter, parameterPrecedence, maxEvents, and exceptionEvent and, for the sake of brevity, won't go into in this article.

Two additional properties, dsn and locale, are specific to this application and aren't part of the standard Mach-II properties (as the others are). Once set, these properties are available to all registered Mach-II components (listeners, plug-ins, etc.). Placing properties in the configuration file allows us to change their values, if needed, without changing any existing (and tested) code.

Next, we register listeners in their section of the configuration file:

<listeners>
    <listener name="StoreListener"
    type="M2PetMarket.m2components.StoreListener">
    </listener>
    <listener name="CheckoutListener"
    type="M2PetMarket.m2components.CheckoutListener">
    </listener>
</listeners>

Here, we've decided to use separate listeners to handle functions related to the basic store and the check out process. Remember, listeners are CFCs and different methods in those CFCs will be called depending on the event and how we wish to handle that event. We'll look at those methods when we get to the section on event handlers.

Our event-filters section is empty - this simple application requires no event filters.

We have two entries in the plugins section, PetMarketPlugin and TracePlugin. The configure method of a plugin is called automatically by Mach-II when the application is first initialized and allows us to write any initialization code. In the case of PetMarketPlugin, configure creates an instance of the Store CFC and calls its superclass, MachII.framework.Plugin, to keep this object in persistent memory.

The Store CFC has the following capabilities:

All plugins extend the base Mach-II Plugin class, which has six plugin point methods. Individual plugins (such as PetMarketPlugin) may override any or all of these methods, depending on where the plugin developer wishes their code to called. PetMarketPlugin overrides two of these plugin point methods, preProcess and preEvent. In the preProcess method, the plugin ensures that a User object exists in the session scope. In the preEvent method, the plugin places the Store object, the User object, and the locale (a property specified in the configuration file) in the current event.

The TracePlugin is used for debugging purposes and provides information about the events processed and the time each took.

In the event-handlers section, we specify how the application should respond to various events (see Listing 1).

These events encompass the entire functionality of the PetMarket application. Let's quickly look at how each event is processed.

The home event uses a page-view element. When a page-view is specified, Mach-II looks for the appropriate display page registered in view-pages (another section in the configuration file) and displays the file(s).

The preferences, about, legal, affiliate, category, product, showCart, and search events are similarly handled, including display pages registered in the view-pages section of the configuration file.

The updateUserPreferences event is a bit more interesting. It notifies a listener, StoreListener, that an event has occurred and passes the Event object to that listener's updateUserPreferences method. Before this, though, it specifies an event-mapping. This requires some explanation (see Sidebar).

To understand what event-mappings are and how they operate, look at the updateUserPreferences method of the StoreListener shown in Listing 2.

This method receives an argument of type, Event. In fact, virtually all listener methods receive this argument. The method then sets the User object's email, favoritepet, mailings_sales, and mailing_tips instance variables. When this is done, the listener places a status message in the event and is done with its work.

Where do we go from here? The original Pet Market application returns the user to the preferences page. Given this, we could announce a new event, preferences, from within the listener, since Listener objects can announce events. But to do so would tightly couple this listener with the flow of the application - something that would violate the very sound principle of loose coupling. Instead, the Listener object announces something quite generic, userPreferencesUpdated.

However, we have no userPreferencesUpdated event registered in our configuration file, nor do we wish to. Instead, we want to substitute our previously registered preferences event for the announced userPreferencesUpdated event, and that's just what using event-mapping allows us to do. When the StoreListener announces userPreferencesUpdated, Mach-II intercepts the event announcement, substituting in its place the event, preferences.

The event handler for addItemsToCart notifies the StoreListener object, calling its addItemsToCart method. The addItemsToCart method within the listener announces a cartUpdated event, for which we have a corresponding registered event.

The event handler for updateCart notifies the StoreListener object, calling its updateCart method. Again, the listener announces an event, cartUpdated. We have a registered cartUpdated event that introduces a new Mach-II XML configuration element, redirect. The redirect tag acts like the ColdFusion tag, cflocation, causing the browser to make a new HTTP request. The redirect tag is used to ensure that if the user clicks the "refresh" button on their browser, the updateCart method will not be called again.

Checkout is handled by the original application by checking the value of a variable, step, that indicates the point in the multi-step checkout process the user is currently in. If we had designed this application originally, we would have used discrete events for each step. Then, if the flow of the checkout were to change later, the change would have been much less disruptive. But we chose to keep the original mechanism intact and so have a startCheckout method.

The event, startCheckout, uses the Mach-II XML configuration element, event-arg, to set a variable,step, in the current event's eventArgs structure. It then displays the checkout display page.

The event, continueCheckout, notifies the CheckoutListener, calling its continueCheckout method. The continueCheckout method has quite a bit of conditional code that, depending on the value of step, performs different functions.

The event, checkoutStep, is a private event, meaning that it cannot be called as part of a new HTTP request - as part of a URL's query string, for example. Events that should not be user-callable (loginValidated, for example) should be marked private.

The checkoutStep event notifies the CheckoutListener, calling its getStatesList. Notice the use of the resultArg attribute, which places the results returned by CheckoutListener's getStatesList method into the current event.

This same event causes CheckoutListener to be notified again, this time calling its getShippingMethods and placing the results of that method call into the current event. Finally, a display page, registered as checkout, is rendered.

When the user wishes to retrace their steps in the checkout process, the previousCheckout event is called. This announces the private event, checkoutStep.

When checkout is completed, the event, completeCheckout, notifies the CheckoutListener, calling its completeCheckout method. (Note that the cases shown where the event name and the listener's method name are the same is not a requirement; it simply made sense to use the same name.)

Within the CheckoutListener's completeCheckout method, another method, checkoutComplete is announced. Again, we use the Mach-II redirect XML element so that a "refresh" button press will not cause the completeCheckout to be called again.

The checkoutReceipt event sets the step variable within the current event to 7 and displays the multi-purpose checkout page.

Our final event, exceptionEvent, will be used whenever the Mach-II framework encounters an untrapped error. Mach-II creates a new event (with the cfcatch information inside of it) and automatically announces exceptionEvent. Application developers will usually want to change how exceptionEvents process exceptions, as the default event handler just displays exception information.

Finally, we arrive at our final section, page-views. In this section, individual display pages are registered. Views are registered so that a change to the path of a view page only needs to be updated once rather than each time the page is used. Page views are given a name (used in the event-handler's view-page element) and a page (the physical location of the file).

<page-views>
    <page-view name="index" page="/views/index.cfm" />
    <page-view name="preferences" page="/views/preferences.cfm" />
    <page-view name="about" page="/views/about.cfm" />
    <page-view name="legal" page="/views/legal.cfm" />
    <page-view name="affiliate" page="/views/affiliate.cfm" />
    <page-view name="category" page="/views/category.cfm" />
    <page-view name="searchResults" page="/views/searchResults.cfm" />
    <page-view name="cart" page="/views/cart.cfm" />
    <page-view name="checkout" page="/views/checkout.cfm" />
    <page-view name="exception" page="/views/exception.cfm" />
</page-views>

Conclusion Recently, one of us received an e-mail that stated, "I'd really like to use Mach-II, but it seems like it's meant strictly for OO gurus and that's not me, at least not yet." While we hope we've given you a sense of how to retrofit an existing application, we really hope we've given you a sense that Mach-II is not just for "OO gurus" or gurus of any kind, for that matter.

Mach-II was created to help working programmers with the problems of maintaining applications as they evolve to increasing complexity. As this issue shows, the growing sophistication of the ColdFusion community has made a number of frameworks available. That's a very encouraging sign and - all kidding aside - we salute Simon for bringing together all framework writers to "lay out their case" for each developer to judge for themselves.

And so, ladies and gentlemen of the only jury that matters, we present (in no particular order) our "Top 15 Benefits of Using Mach-II".

  1. No more spaghetti code. This makes your applications much easier to write, debug, and maintain. Mach-II by design strongly encourages developers to do things "the right way."
  2. Elimination of scope issues. Because all elements in a Mach-II application are geared to get their information from the event, there is no need to worry about whether a particular variable came from the form, URL, request, variables, etc., scope. This makes writing applications clear and simple since all the variables are always in the same place.
  3. Team development. The use of any standardized framework helps teams of developers work together to build software. Mach-II, with its emphasis on separation of concerns among elements, a declarative programming framework, and the use of public APIs, goes the next step in empowering teams.
  4. End to page-centric development. Mach-II's use of the design pattern known as "Front Controller" relieves developers of the fragility of page-to-page application flow. Announce the event and Mach-II handles the rest.
  5. Code organization. Mach-II encourages excellent code organization but doesn't force developers to adopt any particular conventions or standards. A Mach-II application skeleton (available from www.mach-ii.com) shows clear model and view directories, but other organizational schemes can easily be used.
  6. Low-impact maintenance. Mach-II is designed to use the tried-and-true MVC design pattern. MVC separates presentation from logic in the code, thereby making the application flexible and very easy to maintain since changes in one area are localized without breaking the rest of your application.
  7. Encourages object-oriented programming. We think that a mastery of OOP is an absolute necessity for career survival. With the OO tools available in ColdFusion, Mach-II is a perfect way to begin mastering object-orientation in a language you're already familiar with.
  8. Ease of installation. Mach-II is just a CF application. Just drop a single copy of the Mach-II code in a Mach-II directory under your Web root and you're ready to go.
  9. Built on proven principles. Mach-II is based on solid software engineering principles (event-driven, implicit invocation) that have long proved effective in software development. Mach-II brings this power, flexibility, and simplicity to ColdFusion developers.
  10. Code reuse. Mach-II's separation of concerns allows the same model components to be reused across many applications within an organization.
  11. Portal development. Powerful view capabilities allow for simple development of templates or portal-style applications. Multiple views can be built up within a single event and presented to the user at the end of the event.
  12. Framework extensibility. Plugins and filters are extremely powerful, flexible, easy-to-use application components that can handle application concerns such as logging, debugging, security, etc., at the event level without concerning listeners or, worse, business objects. If you need system-wide functionality, you can extend the framework without touching the core files.
  13. Mature and stable. Mach-II is the most mature, stable OO framework for ColdFusion, and has been proven in both small and very large applications in organizations as diverse as the Federal Reserve Board, Dow Jones, and ColdFusion's parent, Macromedia.
  14. Separation of concerns. Underpinning many of the benefits listed above, this fundamental software engineering principle is the foundation on which Mach-II is built. Page view elements are unaware of listener and model elements. Model components know nothing of either listeners or page views - or even of Mach-II. By adherence to the twin principles of loose coupling and tight cohesion, Mach-II applications are built to withstand the changes over time inherent in successful software deployments.
  15. Integration with other frameworks. If you've looked at frameworks like ColdSpring and Tartan, you've probably been impressed with them. We are too. Mach-II's plugin architecture makes it a perfect partner with both current frameworks and others that may appear.
If you're ready to give Mach-II a spin, come to www.mach-ii.com. There you'll find documentation, sample applications, even video tutorials for building Mach-II applications.
© 2008 SYS-CON Media