Development Guide - Mach-II/Mach-II-Framework GitHub Wiki

THIS DOCUMENT IS OUTDATED AND INCOMPLETE!
We are in the process of updating it for Mach-II 1.8. It was originally done by Sean Corfield for Macromedia many moons ago, and it was updated in July of 2007 for Mach-II 1.1.1. Obviously Mach-II has changed a lot since then so much in this guide will be dated, but it's important enough as a resource to get it out there.

Purpose and Overview

The purpose of this document is to provide general guidelines for developing CFML applications using the Mach-II framework. Mach-II is an object-oriented, Model-View-Controller (MVC) framework that implements an Implicit Invocation architecture, and is designed to help CFML developers build applications that are more easily maintained over the life of the application.

This document is an updated and expanded version of the Macromedia Mach-II Development Guide originally written by Sean Corfield. Sean was gracious enough to allow us to take the version he did for Macromedia and update it.

Because of the substantial changes in the newer versions of Mach-II we do not recommend that the original Development Guide is used for anything other than historical purposes. The intent of this document is to supplant the original document for all future versions of Mach-II. Many of the practices outlined in the original document are no longer considered best practices and in some cases functionality outlined in the original Development Guide has been deprecated or eliminated from Mach-II.

Contents

This Development Guide is structured as follows:

  1. Concepts and Core Files
  2. Application Structure and General Design
  3. Designing Models for Mach-II
  4. Designing Views for Mach-II
  5. Designing Event Handlers for Mach-II
  6. Designing Event Filters for Mach-II
  7. Designing Plugins for Mach-II

1. Concepts and Core Files

This section explains the basic concepts behind the Mach-II framework and describes how to access the core files.

Model-View Controller (MVC)

Model-View-Controller (MVC) is the design pattern that forms part of the basis of the Mach-II architecture. In MVC, the presentation (View) is completely separated from the business logic (Model), and the interaction between the Model and the View is handled by Mach-II, which serves as the Controller. The specific CFML constructs applicable to each layer of the MVC architecture are as follows:

  • Views are .cfm files that render HTML (presentation code) and make use of the Mach-II Event object. An individual instance of the Event object is created by Mach-II for each event announcement (see below for details).
  • The Model is a set of ColdFusion Components (CFCs) that represent all the business logic of your application, and these CFCs contain functionality such as the database interaction and business rules of the application. The model objects are not and should not be aware of Mach-II, nor should they interact with Mach-II objects directly. This ensures that your Model is as flexible and reusable as possible.
  • The Controller is, essentially, Mach-II itself. Specifically, index.cfm serves as a front controller, and in conjunction with the mach-ii.xml configuration file and Mach-II Listeners, these items comprise the Controller layer of the application. Each of these elements are discussed in detail below.

An important part of the Controller layer in a Mach-II application are Listeners, which are CFCs that extend the framework's base Listener object and provide the connectivity between the front-end (View) and back-end (Model) of the application. Listeners are also part of the flow control mechanism of the application.

Listener CFCs should always be considered part of the Controller layer, not the Model layer. This is an important distinction to bear in mind as you build Mach-II applications. The Model should always be built in such a way that it is completely unaware of the Controller layer and the Mach-II framework. This ensures that your model is reusable independent of Mach-II, and also that your business logic can easily be used by external technologies such as Web Services, AJAX, remote object calls from Flex, or even another MVC/HTML framework. See "Designing Models" below for additional information.

Implicit Invocation Architecture

In addition to being based on the MVC design pattern, Mach-II is also based on an Implicit Invocation architecture, which is represented by the "II" in the name Mach-II. Please refer to the excellent article entitled An Introduction to Implicit Invocation Architectures by Ben Edwards (creator of Mach-II) for more information.

The basic premise behind implicit invocation is that rather than one page within the application linking directly to another page by a hard-coded URL, each action in the application is an event announcement and during the course of this event various actions occur. Events can be announced either by the user (e.g. a button is clicked) or from within the application code itself (e.g. form processing is complete), and these events are handled by event handlers that are defined in the mach-ii.xml configuration file. The event handlers respond to event announcements by performing business logic and then either presenting a view to the user or announcing another event. Mach-II queues the announced events and processes them, invoking methods on listeners or performing other actions as defined in the event handlers, and this process continues until all the events in the queue have been processed.

Design Patterns

This document makes reference to several design patterns, such as Session Facade, Memento, Transfer Object, and others. Here are some good references on design patterns:

Mach-II Core Files

The Mach-II framework is itself a CFML application, and is simply a collection of ColdFusion Components (CFCs) that are stored in a MachII directory in the web root or a mapped equivalent. A single group of core files can be shared by multiple applications on a single server. In other words, each application that is using Mach-II does not need an independent copy of the framework, though if you choose to have your applications 100% isolated from other applications on the same server, you can certainly include the Mach-II core files in your application's docroot. The version of Mach-II being used in a production environment should be stored in the source code control system (e.g. Subversion) so that all developers agree upon and have access to the same version of the Mach-II core files.

The Role of Application.cfc, Application.cfm, and index.cfm in Mach-II Applications

Mach-II applications use Application.cfc or Application.cfm and index.cfm differently than traditional CFML applications. Mach-II is a front-controller framework, meaning that all requests to the application are routed through index.cfm. Because of this fact, index.cfm will not contain any code that is displayed to the user, and when Application.cfc is used, index.cfm will actually be an empty file.

Using Application.cfm and index.cfm

The use of Application.cfm is more or less traditional in that it defines the application name and other application settings, such as whether or not to enable session management, etc. Where the usage differs is that for the most part, application-wide variables will be defined as Mach-II properties as opposed to being set in the Application scope inside Application.cfm. Additional details about Mach-II properties will be provided below.

In a Mach-II application that uses Application.cfm, the index.cfm file includes the mach-ii.cfm file that is part of the Mach-II core:

    <cfinclude template="/MachII/mach-ii.cfm" />

This allows Mach-II to service all of the requests that come into the application through index.cfm. Some additional settings are included in index.cfm; these are discussed below.

Using Application.cfc (recommended)

Using Application.cfc is recommended in Mach-II applications that will be running on Open BlueDragon, Railo, New Atlanta BlueDragon 7 or higher, and Adobe ColdFusion 7 or higher. (Note that versions of Adobe Coldfusion prior to 7 are not supported.)

An application's Application.cfc must extend the mach-ii.cfc that is included in the core Mach-II files:

    <cfcomponent displayname="Application" output="false" extends="MachII.mach-ii">`

When using Application.cfc, the inclusion of mach-ii.cfm in index.cfm outlined above is instead handled by Mach-II's handleRequest() method. The call to Mach-II's handleRequest() method should be placed in the onRequestStart() method in Application.cfc. In addition, a call to Mach-II's loadFramework() method is placed in the onApplicationStart() method in Application.cfc. Other variables discussed below are set as variables in the default constructor area of Application.cfc.

Starting with Mach-II 1.5, the MachII.mach-ii CFC includes numerous intelligent default settings so you should not have to worry about as many configuration settings as in older versions of Mach-II. Mach-II 1.6 and 1.8 are continuing this trend.

Note: With the use of Application.cfc, an empty index.cfm file is still required; otherwise, 404 errors would be thrown due to the lack of an index file. Also note that although the output attribute of Application.cfc as a whole is set to false, the output attribute of the onRequestStart() method must be set to true, because the page output occurs during the execution of this method.

Mach-II Configuration Settings

Values for MACHII_CONFIG_MODE, MACHII_CONFIG_PATH and, if needed, MACHII_APP_KEY can be customized for each application. In index.cfm this is done prior to including mach-ii.cfm, or in Application.cfc these variables are set in the default constructor area of the CFC. By default, the mach-ii.cfm core file uses the directory in which the request originated as the key in the application scope in which the application's instance of the framework objects are stored. This can be overridden by setting `MACHII_APP_KEY" in index.cfm or in Application.cfc, but it is not generally recommended to change this value.

By default, mach-ii.cfm looks for ./config/mach-ii.xml to use as the application's configuration file, which means that Mach-II will look for the mach-ii.xml file in a config directory relative to the application root, not the web root. The mach-ii.xml file is used to initialize the framework for the application and load the application's Mach-II-aware objects such as Listeners, Filters, and Plugins into the Application scope. The path to the XML configuration file can be overridden by setting MACHII_CONFIG_PATH in index.cfm or Application.cfc.

For security purposes, it is recommended that the mach-ii.xml file be placed outside the web root so that it cannot be accessed through a web browser, in which case the MACHII_CONFIG_PATH variable must be updated to reflect the correct location of the mach-ii.xml file. Alternatively, the file may be left in the config directory but should be renamed mach-ii.xml.cfm so that the raw XML file cannot be browsed directly. If the file is renamed with a .cfm extension, the following line should be placed at the top of the file in order to prevent displaying the contents of the file in a web browser:

    <!-- <cfsetting enablecfoutputonly="true" /> -->

This will allow Mach-II to parse the file correctly as XML, but the CFML engine will prevent the contents of the file from being viewed in a web browser.

MACHII_CONFIG_MODE tells Mach-II when it needs to reload the framework, which also reloads the files within your application that are configured and cached by the framework, i.e. Listeners, Event Filters, and Plugins (more details below). The configuration settings behave as follows:

"1" (always)--the application is reloaded on every request.

Pros: changes to objects managed by Mach-II (Listeners, Filters, and Plugins) are picked up immediately.
Cons: slow, because the framework and objects that extend framework objects are reloaded on every request. This can also hide subtle bugs, because if the listener stores data in the variables scope, it will behave as data in the request scope since the listener is recreated for each request.

"0" (dynamic)--the application is reloaded only when the timestamp of mach-ii.xml changes.

Pros: increased speed, and the listener instance data behaves correctly (i.e. persists from request to request). In addition, it is easy to force a reload by changing the mach-ii.xml configuration file.
Cons: changes to listeners are not automatically picked up; the XML file must be changed in order for the framework to pick up listener, filter, and plugin changes, because Mach-II configures these object types for you and stores them in the Application scope.

"-1" (never)--the application will never be reloaded unless Coldfusion is restarted , the application times out, or a reload is forced programmatically.

Pros: increased speed, and creates a stable production environment because builds can be deployed to productions servers without changing the application behavior until the application is restarted.

Cons: in order for changes to be picked up by Mach-II, ColdFusion must be restarted, or the configuration mode must be changed from -1 to 0 or 1, and then changed back to -1 to put the application back in production mode.

Note that for things such as views, which are not automatically cached or placed in the Application scope by Mach-II, a reload is not required for changes to take effect. Model objects themselves are not aware of Mach-II and therefore would also not necessitate a restart when changed but in practice, instances of Model objects are often created within Listeners, and if this is the case, the application would need to be reinitialized in order for changes to the Model object to be recognized.

A setting of "0" is good for general development because the application will only reinitialize when necessary, which offers improved performance while developing since the entire application does not reload on every request as it would with a configuration setting of 1. In addition, if application state is important during development, having the application reload on every request can cause issues. Note, however, that if a Listener, Plugin, or Filter is changed, and the MACHII_CONFIG_MODE is set to 0, the mach-ii.xml file will need to be updated and saved so the timestamp on the file changes. Otherwise any changes to objects managed by Mach-II will not reload.

With the release of the Mach-II Dashboard, individual application components may be reloaded by clicking a reload button.

It can also be useful to add the following code or something similar to index.cfm, or to wrap the handleRequest() method call in the onRequestStart() method of Application.cfc in this (or similar) code:

    <cfif not request.productionMode and structKeyExists(url, "reloadApp")>
        <cfset MACHII_CONFIG_MODE = 1 />
    </cfif>

This allows the application to be reinitialized easily during development by adding ?reloadApp to the URL. In production, MACHII_CONFIG_MODE will typically be set to -1, meaning it will only reload if the CFML engine or Java web application is restarted or if the Application times out.

2. Application Structure and General Design

This section describes how to structure Mach-II applications and provides some general design guidelines for building object-oriented applications in CFML using Mach-II.

Application Structure

This section explains how to structure applications for Mach-II and contains recommended standard directory structure guidelines.

mach-ii.xml

As discussed in the "Mach-II Core Files" section above, this is the central configuration file for a Mach-II application that specifies all the listeners, events, views, etc. for the entire application. Think of this file as an easy-to-reference roadmap for the entire application. As mentioned above, it should live outside the document root for security purposes, because it may contain sensitive information such as datasource names that should not be exposed to end-users of the application.

The Mach-II Application Skeleton Directory Structure

The Mach-II application skeleton contains directoris for filters, listeners, model, and plugins. This is the minimal directory structure that should be used in order to separate the CFCs by type or concern in an application. If this directory structure is used, please ensure that your Listeners are not considered to be part of the Model layer of your application, and that the model directory contains only business logic CFCs.

Recommended Directory Structure

The directory structure of the Mach-II application skeleton is not required by Mach-II, and a different directory structure adhering to a more standard Java package structure (reverse domain name structure) is recommended. For example, the following would represent a directory structure for a simple contact management application that deals with person-related data:

    {web_server_root}
    appname/
        Application.cfc
        index.cfm
        config/
            mach-ii.xml or mach-ii.xml.cfm (this may also be placed elsewhere as outlined above)
        com/ (this is the root of the Model layer)
            mydomain/
            appname/
                person/
                    Person.cfc
                    PersonDAO.cfc
                    PersonService.cfc
        customtags/ (use custom tags for view-related or presentation logic)
        filters/
        listeners/
            PersonListener.cfc
        plugins/
        views/
            home.cfm
            login.cfm
        MachII/ (Mach-II framework files)

See the "Coding Style Guidelines" document for additional details concerning application directory structure.

Rather than lumping CFCs of different types or with distinctly different concerns in a single model directory, CFCs should be separated and grouped by their function, e.g. listeners, filters, plugins, etc., and the model should further be contained in a reverse domain name directory structure similar to the style of organizing Java packages. Again, the model objects should know nothing about Mach-II, and Mach-II listener objects should not be considered as part of the model layer of your application.

General Design

This section provides guidelines for designing a Mach-II application. General guidelines will be presented in this section, and subsequent sections will address the specifics of designing models (including some basic design patterns), views, event handlers, event filters, and plugins.

Overall Design Considerations

The design of a Mach-II application can be separated into two high-level areas of concern: the business model, and the user interface (UI). In most cases it is possible to perform a certain amount of the design of these two areas in parallel. As will become clear in the sections below, the business model components can be designed and implemented independently of the rest of the application, and should also be implemented independently of the Mach-II framework itself.

The UI can also be designed and implemented independently of the rest of the application. The actions taken by the users in the UI drive the design of the event model in a Mach-II application, and this in turn drives the design of the event handlers and listeners, constrained by the interface of the business model. In teams on which the UI work and backend engineering work are handled by separate individuals or groups, this allows for more efficiency early in the project lifecycle. Furthermore, the loose coupling encouraged by Mach-II allows for more flexibility and easier maintenance later in the lifecycle. Mach-II applications are amenable to change by their design.

Some Basic Object-Oriented Design Guidelines

For many developers, especially those new to object-oriented design, the most difficult part of building an application with OO principles is determining which components are needed. One approach is to write a short, coherent narrative that describes the interaction in the business logic of the application. Most of the nouns in such a narrative suggest themselves as components, and most of the verbs suggest themselves as methods on those components.

Remember that not everything needs to be a component. Some things simply aren't important enough in the overall business model to require a component to model their behavior. In one application a person's name migth be important enough to be modeled as a component that understand title, greeting, first name, last name, middle name or initial, suffix, etc., whereas in another application it may suffice to have a string for first name and another string for last name. There are no hard and fast rules.

Once components are identified, the next step is to consider how well defined they are. Each component should be highly cohesive, meaning it should do (or represent) one thing only and do it well. The components should also be loosely coupled, meaning they should not need to know about or depend upon one another. Be prepared to refactor code throughout the development process in order to improve cohesion and reduce coupling. The first choice of components may be obvious, but it won't necessarily be the best representation of the business logic.

If it is difficult to represent a relationships between two or more components, the relationship between the components itself may be important enough to warrant its own component. In some cases the relationship between components may actually be more important than the components themselves. Also remember that a relationship between two components represents a coupling between those components. Abstracting the relationship into its own component can often reduce the coupling between the components, thereby providing more flexibility.

The other key point to consider in object-oriented design is inheritance versus composition. Many OO novices see inheritance everywhere and try to have components extend other components as a rule. In reality, inheritance is a very special relationship between components: it defines an "is-a" relationship. Inheritance should not be terribly common, and it is also a form of very tight coupling between the parent and child components, so it should not be used unnecessarily. Unless the relationship genuinely represents an immutable "is-a" relationship, where a child component instance is wholly substitutable for the parent component instance in every use case, then you should consider some form of composition instead.

Erich Gamma, one of the authors of Design Patterns: Elements of Reusable Object-Oriented Software http://www.awprofessional.com/title/0201633612, also known as the "Gang of Four" design patterns book, advises to "favor composition over inheritance." Composition can be broadly described as a "has-a" or "refers-to" relationship. A good example of this is the Employee / Manager model. Novices often try to model this with inheritance because a Manager "is-an" Employee, but this model breaks down quickly when promotions and substitutions are taken into account. A more accurate model is that an Employee "has-a" Role, and Manager "is-a" Role. When a regular employee is promoted, it is only really their role that changes and, in some companies, an employee can actually have multiple roles, especially if the company has a matrix reporting system or bestows temporary roles on employees.

Good object-oriented design takes time and practice, so leverage the work of others by learning about and using design patterns, which are really nothing more than proven solutions to common OO modeling issues.

3. Designing Models For Mach-II

This section discusses design issues in the Model layer of a Mach-II application. The Model encompasses all of the business logic in your application and is implemented as a collection of Coldfusion Components (CFCs).

Separation of the Business Model and Mach-II

Mach-II interacts with the Model portion of an application using components that extend the MachII.framework.Listener CFC. In a very simple application, business logic might be implemented in these listener objects. This approach is not recommended, however, because it does not scale well with increasing application complexity. Among other issues, placing business logic in listener objects introduces a tight coupling between the elements of the business logic and the Mach-II framework. If the business components are carefully designed based on the guidelines in the previous section, it should be clear that only a few of those components are natural listeners for events. None of your basic business components are natural listeners. This further illustrates the fact that your listener objects should not be considered as part of the Model layer of your application. Keeping the listeners completely free of business logic is a desired goal. The logic contained in the listeners should be limited to dealing with things such as, for example, preparing form data for submission to the Model layer. Functionality such as applying business rules or communicating with a database should never appear in your listeners.

It is best to ensure that there is only a thin layer of coupling between the framework and the business logic of the application. If necessary, create new components that extend MachII.framework.Listener and whose sole purpose is to communicate Mach-II events to the components within the business model. In other words, try to isolate the business components from the framework components as much as possible following these general guidelines:

  • Only listener components should know about the Mach-II framework
  • Only listener components should access Mach-II properties
  • Only listener components should announce events

The core business components should know nothing about Mach-II and should have no dependencies on the framework whatsoever.

In the vast majority of applications it will likely make sense to create a service layer to sit between the Mach-II listeners and the core business components. This abstracts the business logic from Mach-II even further, and also provides a convenient API through which something other than Mach-II (e.g. Flex, web services, AJAX, etc.) can access the business logic of your application. In short, put as little logic in your listeners as possible and create an API-based service layer between the listeners and the true Model layer of your application. This ensures maximum abstraction and reusability of your business logic components.

Anatomy of a Listener

The following is an example of a minimal Listener CFC that uses the EventInvoker invoker type (which is the default invoker type as of Mach-II 1.1.0):

    <cfcomponent extends="MachII.framework.Listener" />

        <cffunction name="configure" access="public" output="false" returntype="void">
            <!--- perform any initialization --->
        </cffunction>

        <cffunction name="someMethod" access="public" output="false"
            returntype="[SomeType?](/machii/wiki/SomeType)">

            <!--- take the Mach-II event object in as an argument so we have
                access to URL and form data --->
            <cfargument name="event" type="MachII.framework.Event" required="true" />
                <!--- perform additional tasks here as needed, then return result --->

            <cfreturn instanceOfSomeType />
        </cffunction>
    </cfcomponent>

The configure method of the listener is invoked once (and only once) when the application first initializes, and this is handled automatically by Mach-II. Any dependencies, instance variables, or other tasks related to the initial setup of the listener should be placed inside this configure method.

The method someMethod() can be invoked from an event handler declared in mach-ii.xml by using the <notify> command. In the example above, the returned value instanceOfSomeType should be of type SomeType. A return type of any should only be used if the method truly requires a dynamic return type. If you know the data type that should be returned by the method, whether that be a native Coldfusion datatype such as a query or a custom data type as in the example above, that type should be declared as the return type in the method definition. Note that the return type can be set to void if the method does not return anything.

A listener is declared in mach-ii.xml as follows:

    <listener name="listenerName" type="Path.To.YourListener" />

This imples that the default EventInvoker invoker type will be used, which places all the URL and form variables into an instance of MachII.framework.Event. This object is then passed to the invoked listener method as a single argument, making the event data available to listener methods. This is typically the desired behavior in a Mach-II application.

If a different invoker type is required, it is declared as follows:

    <listener name="listenerName" type="Path.To.YourListener">
        <!--- declaration of invoker type --->
        <invoker type="MachII.framework.invokers.EventArgsInvoker" />
    </listener>

The <invoker> tag specifies how the methods are invoked on the listener. In the example above, the EventArgsInvoker type that ships with Mach-II is used. This invoker passes URL and form variables to listener methods as individual named arguments as opposed to placing them all in an instance of MachII.framework.Event. See the "Invokers and Listeners" section that follows for more details.

Note that the older CFCInvoker_Event.cfc and `CFCInvoker_EventArgs.cfc" have officially been deprecated and will be removed from a future version of Mach-II.

You may optionally specify parameters within the listener declaration:

    <listener name="listenerName" type="Path.To.YourListener">
        <parameters>
            <parameter name="param1" value="value1" />
            <parameter name="param2" value="value2" />
        </parameters>
    </listener>

This provides default parameter values for param1 and param2 of value1 and value2 respectively. These can be accessed within the listener using the getParameter() method, e.g. getParameter("param1"). The listener inherits this method from the MachII.framework.Listener base class.

The methods of a listener CFC are invoked in an event handler as follows:

    <event-handler event="someEvent" access="public">
        <notify listener="listenerName" method="someMethod" resultArg="someVariable" />
    </event-handler>

This causes the someMethod() method to be called within the context of the current event, and the returned result from the call to someMethod() is stored in the someVariable variable that is put into the Mach-II event object. This variable is then accessed from the event object using the event object's getArg() method. In this example, you would access someVariable from the event object by calling event.getArg("someVariable"). Note that the resultArg attribute is optional and should not be specified for methods with a return type of void.

In versions of Mach-II prior to 1.1.0, resultKey was used as opposed to resultArg, and often the request scope was often used as a data bus instead of using the event object. This syntax is still supported through the use of the old invokers, but is not supported by the new invokers. Remember that the old invokers have been deprecated and will be removed in a future version of Mach-II, so other than situations in which an older version of a Mach-II application is being deployed on a newer version of the Mach-II framework, the old invokers should not be used. It is our recommendation that you update all old code to take advantage of the new invokers and new resultArg syntax.

A listener only has access to the current event, but additional events can be added to the event queue by calling announceEvent() from within the listener. Events added to the queue are executed at some point after the current event has been handled. A listener method typically calls one or more methods on one or more business model objects. The business model objects might be created on the fly, such as with something like an instance of a bean; they might be managed in a shared scope such as the application or session scope; or they might be created within the listener's configure() method (which is called automatically by Mach-II when the application first initializes) and then stored in the listener's variables scope.

For more information on writing listeners, refer to Intro to Mach-II Listeners

Invokers and Listeners

When a listener is declared in mach-ii.xml, if an invoker type is not specified the default EventInvoker is used. As explained above, the EventInvoker passes the current event object to the invoked method as a single argument. In versions of Mach-II prior to 1.1.0, declaring an invoker type for each listener was required. Declaring an invoker type is now only required if using an invoker other than the default EventInvoker, which is typically not necessary.

The invoker affects how arguments are passed to the listener methods in a <notify> tag, and also affects what happens to the data returned from the method called. As noted above, the default EventInvoker causes the whole event object to be passed as a single argument of type MachII.framework.Event to the method, whereas the EventArgsInvoker causes all of the event arguments to be passed to the listener method as separate named arguments. Both invokers store the result of method calls in the variable specified in the resultArg attribute of the <notify> command, and this variable is in turn put into the event object. In the majority of cases the default EventInvoker will be used, but there are some issues to consider:

  • EventArgsInvoker may seem to provide better type safety since the type of each argument, whether or not each argument is required and, if not, whether or not the argument should have a default can all be specified. However, letting the CFML engine itself validate what in practice amounts to the URL and form data is not very robust. Particularly for data that will be put into the application's database, more robust data validation is necessary.
  • EventInvoker provides more flexibility, but at the expense of requiring additional code inside your listener method to access and validate the event arguments. In the case of the EventArgsInvoker, each event argument is accessed as arguments.argName. The EventInvoker encapsulates individual arguments into a single event object, and the event arguments are then accessed via the getArg() method of the event object. For example, to access an argument called argName, the following method call would be used: arguments.event.getArg("argName")
  • In general, use the default EventInvoker because it provides a great deal of additional convenience, and consider using event filters (more details on event filters are provided below) or other type checking to enhance the type safety of your application.

When the EventInvoker is used, the listener method is passed a single argument that should be declared as follows:

    <cfargument name="event" type="MachII.framework.Event" required="true" />

In order to test whether or not a given URL or form variable was provided, use the isArgDefined() method on the event:

    <cfif arguments.event.isArgDefined("anArg")>
        ...
    </cfif>

Since URL and form variables are inherently strings as far as HTTP is concerned, it is better to explicitly validate the event argument types in the code itself rather than using EventArgsInvoker and declaring method arguments for individual URL or form variables. The CFML engine will attempt to (and typically does a good job) of type converting form and URL variables, but if for some reason the type cannot be converted, for example, from a string to a numeric data type, the CFML engine will throw an error when it encounters the method argument containing the wrong or inconvertible data type. This gives you less control over validation and error handling.

Custom invokers may be created if an application requires behavior other than that which exists in the invoker types that are provided with the framework.

Instance Data and Listeners

Since Mach-II loads all the framework component instances (i.e. listeners, filters, and plugins) into the application scope when the application is initialized, bear in mind that any instance data created in a listener will effectively be stored in the application scope. This has three main implications:

  1. Pro: You can cache data for the application very easily by storing it as instance data within a listener's variables scope.
  2. Con: Updating instance data affects all threads and should therefore be locked appropriately. Use <cflock type="exclusive" name="..."> ... </cflock> around any updates on data in the variables scope, with an appropriately chosen lock name.
  3. Con: To manage per-session data, implementing some sort of Session Facade is desired. See below for additional details.

In general, listeners should be stateless, meaning they should contain no instance data unless data is specifically being cached for performance reasons. One example of this is saving property values in the variables scope to avoid accessing the properties dynamically in each request. Consequently, extra care must be taken to use var to declare all local variables in listener methods so that variables aren't unintentionally stored in the unnamed scope. Remember that tags such as <cfquery> create variables too, so these must be var scoped as well:

    <cfset var userSelect = 0 />

    <cfquery name="userSelect" ...>
        ...
    </cfquery>

Session Facade

As mentioned above, if per-session data is used within the application, the Session Facade design pattern will be used. By using a Session Facade pattern, only the listener is session-aware, meaning that the listener knows about the session scope and manages component instances that live in session scope, but per-session component instances are not listeners themselves and therefore should not reference the session scope directly.

For example, a shopping cart listener would respond to events such as addItem, removeItem, and updateQuantity, but it would delegate the actions to a cart object that is stored in the session scope. The shopping cart listener would create the cart object in the session scope on demand. The cart object would store information as instance data (which is per-session because the cart is per-session) but would not reference the session scope.

    <cfcomponent displayname=”CartListenerextends="MachII.framework.Listener">

        <cffunction name="addItem" ..>
                <cfargument name="item" ../>
                <cfset getCart().addItem(arguments.item) />
        </cffunction>

        <cffunction name="getCart" returntype="Cart" access="private">
            <cfif not structKeyExists(session,"cart")>
                <cfset session.cart = createObject("component","Cart").init() />
            </cfif>
            <cfreturn session.cart />
        </cffunction>

    </cfcomponent>

The partial example above demonstrates the Session Façade technique but is not intended to be production quality; for example, there's no configure() method for the listener, there's no hint= or output= attributes, it doesn't lock session scope when creating the cart object which might be needed if you are concerned about threaded access for a single user (which may or may not be a concern depending on the application).

Transfer Objects

The basic event lifecycle in Mach-II is as follows:

  1. An event is announced
  2. Listeners are notified and subsequently return data to the event object as a resultArg
  3. A view is rendered that utilizes and/or displays the event data, or another event is announced

When more than one piece of data is needed by a view, the listener could be notified multiple times, creating several resultArgs that are subsequently put in the event object that is available to the view. This may seem like the obvious approach, but there are several negative aspects associated with this approach:

  • Multiple calls are made to methods on the listener, causing possible performance issues
  • A very granular interface is required within the listener, meaning lots of low-level getters
  • Creates complex dependencies between views and input variables, because numerous separate variables within the event object are required

These issues should raise red flags, particularly the last two, which cause encapsulation to break down and coupling to increase. There is nothing wrong with notifying multiple listeners in a single event, or even the same listener multiple times, if it’s truly necessary. When the data is related, however, the Transfer Object pattern provides a better solution to this problem.

When is this pattern likely to occur within an application? A good example is a view that displays information about a person. The view probably needs to display first name, last name, street address, city, state, zip, and so on. The obvious (but naïve) approach would be to have getter methods for each of these pieces of data within the listener - which necessitates separate calls for each of these listener getter methods in the event handler - and have the view depend on event.getArg(“firstName”), event.getArg(“lastName”), event.getArg(“streetAddress”), and so forth. In addition to the problems outlined above, this is not a particularly elegant solution.

The Transfer Object design pattern is intended for situations such as this, and it solves the problem by aggregating data into a single object that is passed between the model and the view. In the above example, a single getPerson() method would return a single struct that contains all of the data needed, and the view would then have a single dependency on that one event argument. For example, if the getPerson() method in the listener returned a struct that was put in a resultArg called person, this struct would be available to the view by calling event.getArg(“person”).

If greater encapsulation is desired or functionality beyond what a struct can provide is required, or if an application employs a complete object model, beans are used as the Transfer Object. A bean is a simple CFC containing getters and setters for the data as well as a constructor (init()) that the listener component uses to set all the data in the transfer object before returning it to Mach-II. Beans are extremely common in OO applications. See the “Beans and Form Handling” section below for additional information about beans.

Beans and Form Handling

As indicated above, a bean is a simple CFC with getters and setters that encapsulates the data (a.k.a properties or attributes) within the bean. Beans are typically used as Transfer Objects to pass data between different layers in an application. If a bean has a property foo (a private instance variable), then it also has the methods getFoo() and setFoo() to get and set, respectively, the value of foo. The getFoo() method will be public; the setFoo() method may be public or private depending on whether the bean is considered read-only or read-write. Here is a simple read-only bean:

    <cfcomponent>
        <!--- declare properties for clarity: --->
        <cfset variables.foo = "" />

        <!--- constructor: --->
        <cffunction name="init" returntype="FooBean" access="public" output="false">
            <cfargument name="foo" type="string" default="" />
            <cfset setFoo(arguments.foo) />
            <cfreturn this />
        </cffunction>

        <!--- public getters: --->
        <cffunction name="getFoo" returntype="string" access="public" output="false">
            <cfreturn variables.foo />
        </cffunction>

        <!--- private setters: --->
        <cffunction name="setFoo" returntype="void" access="private" output="false">
            <cfargument name="foo" type="string" required="yes" />
            <cfset variables.foo = arguments.foo />
        </cffunction>
    </cfcomponent>

A read-write bean differs from the above example only in that the setters are public. The constructor has an optional argument for each property and calls setXxx() for each property xxx.

When a form is submitted within a Mach-II application, Mach-II supports bean creation and population through the <event-bean> command:

    <event-handler event=”processForm” access=”public”>
        <event-bean name="beanName" type="beanType" fields="field1,field2" />
    </event-handler>

This command creates a bean of the specified type (beanType, e.g., my.model.Foobean and stores it in the current event object as an event argument called beanName (e.g., fooBean or just foo).

If fields= is specified, the <event-bean> command calls the bean’s constructor (the init() method) with no arguments and then calls the setter for each field specified in the list of fields. Using the <event-bean> example above, this would create a bean of type beanType, call the bean’s init() method, and then call the setXxx() method for each field and pass it the appropriate data from the form post. In this case the set method calls would be as follows:

    setField1(event.getArg("field1"))
    setField2(event.getArg("field2"))

Note that for the event bean to be populated properly, the field names in the form must match the property names in the bean. For example, if a Person bean has the properties firstName and lastName, and an <event-bean> command is used to populate a Person bean with form data, the form fields must be named firstName and lastName in order for the form field data to be used to automatically populate the bean. Also note that there must not be spaces between the list of field names in the event bean command (fields=”field1,field2” NOT fields=”field1, field2”).

If fields= is omitted, the <event-bean> command calls the bean’s constructor with all the current event’s arguments by name (e.g., theBean.init(field1=event.getArg("field1"), field2=event.getArg("field2")).

The <event-bean> command makes it very easy to handle form submissions in Mach-II. Simply define a bean component to represent the data in a form, ensure that the form fields and bean property names match one another, and use the <event-bean> command to populate the bean using the form data that is submitted. The submitted data can then be operated upon via an encapsulated bean, which allows for performing validation (see “Designing Event Filters” below), persistence, or whatever additional functionality is required.

For more information on writing and using beans, refer to the article Beans, Beans, the Musical Fruit.

Database Access Objects (DAOs)

Although this topic is not directly related to the Mach-II framework, most applications need to implement data access, typically to a relational database, so providing guidance on best practices within the context of Mach-II does not seem beyond the scope of this document.

Note that in the original version of this guide it was recommended to split this functionality into a separate DAO and Gateway objects. THIS IS NO LONGER RECOMMENDED. This artificial splitting of database-related functionality into two separate CFCs adds unnecessary complexities and is to be avoided.

There are two basic patterns of access to persistent data within most applications:

  1. Aggregated access: typical in reporting, searching, or any process that retrieves multiple rows from the database
  2. Per-object access: used for creating, editing, and working in depth with a single row of data

CFML has an excellent built-in idiom for dealing with aggregated data, namely the query object, which provides an efficient way to manipulate potentially large sets of data retrieved from a database or other data source. When dealing with aggregated data it usually does not make sense to convert every row returned into a fully encapsulated object, because the typical use case for aggregated data is to display tabular results to the user, or serve as the "master" list in a master-detail scenario. Using an object for each database row in these situations adds unnecessary overhead and complexity to the process, so unless there is a specific need to use a collection of objects, use the query object for aggregated data.

When dealing with what corresponds to a single row in a query, however, it usually does make sense to interact with a fully encapsulated object, since the interest at this level is in working with a specific object and its data. It is in dealing with individual records from the database that the standard Create, Read, Update, and Delete (CRUD) methods begin to appear.

The difference between returning a query object for aggregate data and returning a bean can be illustrated with a couple of examples:

  • Given a business model object called Order, an OrderDAO component for both per-object access and aggregate data would be created.
  • Aggregate data methods such as findAll(), findWhere(), and findByID() would all return standard query objects (even findByID() which may return a single row).
  • CRUD methods such as create(), read(), update(), and delete() deal with single records, although it is not required the methods be named this way. (It is not uncommon, for example, to combine the create() and update() methods into a single save() method that will create a new record or update an existing record as needed.) These methods would operate on a specific Order bean as opposed to a query object, exchanging data via the getters and setters in the Order bean, or via some other type of snapshot of the Order's data.

To expand on the final point above, the Order bean could also implement methods such as getSnapshot() and setSnapshot() that would interact with a lighter-weight representation of the bean's data, such as a struct or some other construct implementing the Memento design pattern. This data is less encapsulated but could provide improved performance when necessary, and might also serve as an easier conduit between the CFML engine and a web service when the full bean data type is not necessary.

Separating these operations from the business model objects themselves helps the business objects remain persistence-neutral, so that if the method by which data is persisted ever changes, the business objects themselves will not need to be altered. The DAO components can be optimized for retrieving large record sets, caching, etc. for aggregate data, as well as dirty data updates, pooled object access, and so on for single record access.

One alternative to the DAO pattern is the “active record” pattern that is probably best known from its implementation in Ruby on Rails. In the active record pattern the business model objects themselves would contain CRUD methods, meaning that they would know how to persist themselves. Many Coldfusion Object-Relational Mapping (ORM) frameworks such as Reactor and Transfer use the active record pattern, whereby all of your business objects extend a single object from the ORM framework, and the base ORM object provides the necessary persistence methods such as read(), save(), and delete().

The pros of implementing CRUD in your business model object are:

  • Fewer components to keep track of; no separate DAO components
  • No need to implement data transfer machinery; the CRUD methods will have direct access to the data within your business model object

The cons are:

  • SQL is mixed in with your business logic in the same component, which removes encapsulation of the persistence layer itself.
  • The business model object is heavier, more complex, and less cohesive.
  • Makes it more difficult to change the persistence layer to use a different data source or completely different persistence mechanism altogether, because the persistence mechanism is coupled to the business model object. Having a separate DAO layer allows you to persist some business model objects one way while persisting others in a different way, or to easily change the persistence mechanism altogether.

It is the driving forces of good encapsulation, high cohesion, and loose coupling that lead to the recommendation above. Providing DAO components that are separate from the business model components creates maximum flexibility in the business model components, and also reduces coupling between business model objects and the persistence layer of the application.

4. Designing Views For Mach-II

This section discusses design issues in the View layer of a Mach-II application. The View encompasses all of the HTML user inteface (UI) for a Mach-II application and is implemented as a collection of .cfm pages.

Controller / View Data Transfer

Views should not contain any logic other than presentation-specific logic. As an example, a view should never contain a <cfquery>, but logic such as looping over a query for display purposes will obviously be contained in views. Dynamic data required by a view should be passed in from the controller (Mach-II) via the event object, which is accessible from a view by referring to event. The section covering Transfer Objects above touches on this topic and recommends that views depend on as few variables as possible to maintain encapsulation and reduce coupling.

An important point in this discussion is that the arguments in the event object that are used by a view define the API between the controller and the view, and thus specify a contract that should be agreed upon early in the design, documented, and then honored for rest of the project lifecycle if possible. This helps maintainability and stability. There is no way around the fact that the view must call data by name from the event object (e.g. event.getArg(“myData”)), so any changes to the event object’s variable names (i.e. the resultArg declaration in the notify command, or variables that are explicitly put in the event object programmatically) will require a corresponding change in the views that use this data.

Parameterized Views and eXit Events (XEs)

A good use of event arguments in a view is when <event-arg> is used to set values for parameterized views, such as forms that support both edit and create operations. This is a better approach than hard-coding the submit and cancel actions into the view itself.

Fusebox uses a similar technique that is referred to as XFAs (eXit FuseActions). By analogy, these constructs in Mach-II could be described as eXit Events (XEs). The use of XEs reduces coupling between views and the application control flow, so it's good practice to parameterize views where appropriate. In this way, the exit paths out of a view (i.e. links and form actions) are passed in as event arguments.

For example, a paged record set view might have previous / next page links like this:

    <a href="index.cfm?event=#event.getArg('XEPrevious')#">Previous</a>
    |
    <a href="index.cfm?event=#event.getArg('XENext')#">Next</a>

This allows the view to be invoked in different parts of the application with the previous / next events passed in from <event-arg> commands contained in the mach-ii.xml file.

contentArg Considerations

Mach-II allows views to be rendered into contentArg variables so that a single HTML page can be constructed from multiple views or page fragments. These contentArg variables are stored in the event object so they also form part of the API between the controller and the views, as discussed above. The same caveats outlined above therefore apply.

As a general guideline, the application will be more maintainable if dynamic data (i.e. data from the event object) is passed into simple views that are rendered into contentArg variables, and these contentArg variables are then aggregated into a single layout template view. For example:

    <event-handler event="showFoo" access="public">
        <notify listener="foo" method="getFoo" resultArg="fooData" />
        <view-page name="fooPage" contentArg="content" />
        <view-page name="mainLayout" />
    </event-handler>

In this example, the API between fooPage and the controller is simply event.getArg(“fooData”) (which itself might be a struct or object containing multiple items), and the API between mainLayout and the controller is simply event.getArg(“content”). There is no dependency between mainLayout and any dynamic data, because all the dynamic data is rendered into HTML by other views prior to rendering mainLayout.

If several views are combined to create part of a layout, in some cases the event handler can be simplified by appending content to a single contentArg as the event is processed:

    <event-handler event="showFoo" access="public">
        <notify listener="foo" method="getFoo" resultArg="fooData" />
        <view-page name="fooPod" contentArg="content" />
        <view-page name="barPod" contentArg="content" append="true" />
        <view-page name="mainLayout" />
    </event-handler>

In this example, the output of barPod is appended by Mach-II to the output of fooPod within the content variable contained in the event object, so there is no need to use an additional contentArg or different layout.

The desired goal is that each view focuses on rendering only the data on which it depends, and a layout view is used to assemble the finished page from pre-rendered HTML fragments. This keeps each view cohesive, because each view does one job well. This also reduces coupling between the views themselves and between the views and the controller. The next section discusses this principle in a bit more detail.

Portals, a.k.a. Grid Layouts

In a typical application the user interface is quite complex and often has several dynamic elements. The navigation can be dynamic, for example, adapting to the user’s current location within the application. Develop Mach-II user interfaces with an eye towards breaking them down into smaller, simpler parts that form a grid. Most portal sites (e.g., My Yahoo!) are good examples of grid layouts. They have multiple columns, each containing multiple sections that are often referred to as pods.

When developing the UI for an application, implement each of these pods as a separate view that renders event arguments into a contentArg, and use a layout view to assemble the contentArg variables into the finished HTML page. This will produce small, focused, cohesive views that are loosely coupled. This also creates views and user interface elements that are more likely to be able to be reused on other pages or potentially even in other applications.

Views can be reused in Mach-II applications very easily as long as they have only narrow, well-defined dependencies. Even if pods contain only static content, it is still usually better to implement them as views so that you can rearrange page elements more easily through simple changes to the final layout view. This situation is sometimes referred to as “view stacking,” but is really an implementation of the Composite View design pattern; see http://java.sun.com/blueprints/corej2eepatterns/Patterns/CompositeView.html for details.

This discussion raises the question of how best to handle headers and footers. Should they be implemented as separate pod views and assembled by the layout view? If the header and footer are application-wide elements, the chances are that they will only change if the whole look and feel of the application changes. Implementing them as separate views is possible, but would create a situation in which every event-handler that generates HTML looks something like this:

    <event-handler event="..." access="...">
        ...
        <view-page name="..." contentArg="content" />
        <view-page name="header" contentArg="header" />
        <view-page name="footer" contentArg="footer" />
        <view-page name="mainLayout" />
    </event-handler>

A better alternative to reduce code duplication in mach-ii.xml is to announce the final layout view as an event. For example:

    <event-handler event="..." access="...">
        ...
        <view-page name="..." contentArg="content" />
        <announce event="layoutPage" copyEventArgs="true" />
    </event-handler>

    <event-handler event="layoutPage" access="private">
        <view-page name="header" contentArg="header" />
        <view-page name="footer" contentArg="footer" />
        <view-page name="mainLayout" />
    </event-handler>

NOTE: With the introduction of subroutines in Mach-II 1.5, using subroutines as opposed to announcing layout events is the preferred method. The guide will be updated to reflect this.

Every event-handler that needs to generate HTML would announce the layoutPage event with the copyEventArgs attribute set to true. The contentArg that is built up in the original event is then passed to the layoutPage event and finally rendered by the mainLayout view.

This latter style is recommended as a general rule because of the reduction of redundant code in mach-ii.xml as well as the flexibility it provides. However, specific situations may dictate a different approach. If the headers and footers are closely tied to the overall page layout, then they can either implemented directly inside the layout view or implemented as separate files that are included in the layout view:

    <!--- doctype etc goes here --->
    <html>
        <!--- head / title etc go here --->
        <body>
            <cfinclude template="header.cfm" />
            <cfoutput>#event.getArg("content")#</cfoutput>
            <cfinclude template="footer.cfm" />
        </body>
    </html>

Custom tags imported as tag libraries may also be used to control the overall rendering of pages:

    <!--- set up page settings etc --->
    <cfimport taglib="/customtags/view/" prefix="view" />

    <view:renderpage>
        <cfoutput>#event.getArg("content")#</cfoutput>
    </view:renderpage>

NOTE: Mach-II 1.8 introduces form and view custom tag libraries. The guide will be updated to include examples.

Views and Scopes

By following the recommendations above, views in the application should largely be referencing only the current event object to access data. The event object is effectively the interface between the controller and the view. All of the typical CFML scopes are still available in the view, however, so the following provides some general guidelines as to whether or not other scopes should be accessed directly from views.

  • application and server scope: no

Simply from the perspective of encapsulation, views should not reference these scopes; in fact, almost no code should access these scopes directly. Listeners and other parts of the framework are already stored in application scope and therefore can manage application scope data as instance data, and this data can then explicitly passed out via the event object (resultArg) to be made available to views. Server scope should only be used for cross-application caching and should be encapsulated in a listener and, again, passed via the event object to views.

  • CGI scope: no

Variables in the CGI scope tend to vary from web server to web server and can be affected by a variety of configuration issues. They may drive the logic in an application but should not directly drive the appearance of an application; therefore, they should not be accessed within a view. If different layouts need to be selected based on, say, the user's browser, this can be managed by using a listener to announce different events based on the CGI variables and handling those events by rendering different layouts.

  • cookie scope: no

Since cookies are generally used to drive behavior rather than layout, use of cookie scope inside a view should be avoided. If a cookie's value is intended to be displayed in a view, it should probably be passed through a listener for validation and into the view via request scope or the event object rather than being directly accessed in the view.

  • session scope: yes

If an application is tracking data in session scope and a view needs access to that information, it is acceptable to directly refer to session scope within a view. Strictly speaking, encapsulation would oblige the use of a listener or a filter to copy the necessary data into the event object for the view, but in applications that rely on session data extensively this is likely to be too cumbersome in terms of code complexity and may not be worth the extra effort. Not only would this necessitate copying all the necessary session scope data into the event object, thereby expanding the view API, but if operations are performed that might update the data, it needs to be synchronized back into session scope or needs to be accessible by reference rather than by value. If the application has only limited reliance on session scope, a filter can be used to copy session variables to the event object with little overhead. As a general rule for good encapsulation use a Session Façade pattern for accessing the session scope, but accessing the session scope from a view is not prohibited.

  • URL and form scope: no

Since data from these scopes is automatically transferred to the event object, views should never reference URL or form scope directly. Form data should be handled through a bean that aggregates the form into a single event argument, either by using the <event-bean> command or a filter like the ContactBeanerFilter in the Contact Manager sample application. Views can retrieve URL and form scope values from the event object using event.getArg("argName").

5. Designing Event Handlers For Mach-II

This section discusses design issues in the event-handlers section of the mach-ii.xml configuration file. If the recommendations in the “Overall Design Considerations” section are followed, the final design and implementation of both the core business model components and the HTML views is likely complete by the time event handlers are developed. With respect to designing the views, simple interaction schematics should be a sufficient level of detail to enable work on designing the event handlers to begin.

Every action the user may take within the application corresponds to an event. Every link and every form submission is an event, including the default event implied when an event is not explicitly included in the URL. Since the public event names will be a visible, user-facing part of the application, it's worth choosing readable event names of the form verbNoun, similar to the typical naming convention for method names in a CFC. Use mixed case for event names, because it makes them more readable. Event names are not case sensitive in Mach-II so it doesn't matter if a user mistypes such a URL as verbnoun. An acceptable alternative is to use verb.noun, which might be more familiar for developers who come from a Fusebox background since Fusebox uses circuit.fuseaction in the URL.

The events implied by user interactions form the first tier of events in your application. The essence of Implicit Invocation is to abstract the control flow between components into the event model; thus, a large number of events are implied by the application logic. For example, if an event has conditional flow, such as a login event that can succeed or fail, typically event handlers will be created for each of the conditional outcomes. See “Application Neutral Events” below for additional details. Even when the flow is linear, it is often worth separating the initial user event processing from any subsequent application flow such as view rendering.

Event handlers should generally be short and self-contained. Again, this is driven by the desire to have high cohesion and loose coupling. Name the events to reflect this internal partitioning, e.g., sectionVerbNoun or section.verbNoun. As a specific example, an application may have a showMainMenu event for both the public side of the application as well as the administrative side. The public event might simply be showMainMenu, while the administrative menu could be admin.showMainMenu. A prefix such as admin. can also be leveraged in the creation of a plugin to selectively secure various parts of the application.

Although event handlers should be short and self-contained, they should not be broken up unnecessarily. If there is a sequence of listener method calls that belong together, use <event-arg> to chain listener calls rather than breaking up the sequence into a series of artificial private events.

Public and Private Event Handlers

The events caused by user action are only part of the picture in a Mach-II application. These are public events generated by the user’s interaction with the application. The event handlers for these events should be declared public by using the access="public" attribute of the event-handler command. All other event handlers, meaning those that handle events generated by the application itself as opposed to events initiated by the users, should be declared access="private" so they cannot be announced via the URL by the user. The event handler for the default event, which is the event that is invoked if the user does not explicitly specify an event in the URL, should be declared access="public". Even though by default events without an access declaration will be made public, it is good practice to explicitly declare the access for each event for the sake of clarity and readability.

Declaring event handlers as private ensures they cannot be invoked via the URL. For example, after a successful login attempt, a private event called loginSucceeded might be announced. It would not be wise to make this event public because it could be announced by any user via the URL. Granted, if someone had not logged in the application likely wouldn’t recognize them even if they did announce loginSucceeded via the URL, but it is simply good practice to make all events that do not need to be announced via the URL private. Another benefit is that if certain events cannot be announced via the URL, data validation is less of a concern because the application itself will be the only data provider for these events. As an aside, event filters may be used for data validation as well as manipulating event data. See the “Designing Event Filters For Mach-II” section below for further details.

In most OO programming languages it is generally recommended to declare all public entities first with private entities last. That is also good practice to follow for event-handler declarations, with each group in alphabetical order so that event handlers are easier to find within long mach-ii.xml files.

Application-Neutral Events

If a listener needs to announce an event, choose an event name that suits the listener rather than the application as a whole. In other words, use application-neutral events. The event-mapping tag can be used to map the listener's events to those recognized by the application. This is once again driven by the concepts of cohesion and coupling to improve reuse and maintainability. The use of application-neutral events helps decouple the listener from the application that uses it and may allow for the listener to be reused in other applications.

For example, a login listener should announce loginSucceeded or loginFailed (or more specific failures such as loginBadPassword, loginNoSuchUser, etc.), and the event handler should then map these events to specific events that are appropriate to the application:

    <event-handler event="adminLogin">
        <event-mapping event="loginSucceeded" mapping="showAdmin" />
        <event-mapping event="loginFailed" mapping="showLogin" />
        <notify listener="adminListener" method="login" />
    </event-handler>

In this example, the login() method of adminListener would announce the application-neutral event loginSucceeded or loginFailed through a call to announceEvent() within the listener code, but the event mappings would cause this event announcement to be translated so that what is placed in the event queue is showAdmin or showLogin respectively.

The event mappings are active only from the <event-mapping> command to the end of the event handler, therefore the <event-mapping> command must precede the listener notifications that it is intended to influence.

Handling Shared Execution Paths

It is quite common to see similar code across several event handlers as an application is developed. For example, Edit and Create operations tend to have a similar flow beyond the initial capturing and loading of data, which can be seen in the Contact Manager sample application. Building complex layouts is another operation that tends to create common code at the end of several event handlers. In each of these cases, consider moving the common operations into a separate event handler and simply announcing a new common event from each of the original event handlers.

Even if the code isn't identical, the variables can often be abstracted and turned into common code. In this situation, the original event handlers will perform a certain amount of setup. For example, <event-arg> can be used to define values that differ between the two code paths, and then a new common event could be announced.

In both cases, the common event should be declared access="private" following the guidelines above, because these common events would never need to be announced via the URL. The intent of this discussion is to reduce code duplication and look for code reuse.

Decomposing and Chaining Event Handlers

If an event handler is separated into multiple private event handlers, some thought must be given as to how to data will be passed between the independent events. For example, assume the following event handler:

    <event-handler event="processForm" access="public">
        <announce event="handleFormData" />
        <announce event="displayResult" />
    </event-handler>

Since handleFormData and displayResult are both announced from the processForm event handler, they are both new event objects which both have a copy of the event arguments from the processForm event object. However, any changes made to the event object inside the event handler for handleFormData will not be reflected in the event object that is passed to the displayResult event handler, because handleFormData and displayResult are distinct events. This means that if communication between these two event handlers is desired it must be addressed in some way since this communication does not occur automatically.

In this case the ability to chain the events together could be useful. In other words, chaining the events together would take the implicit sequence from the code above and turn it into a more explicit sequence. The sequence above is implicit because it relies on the event queue to process handleFormData first and then displayResult second. Remember, however, that the event queue does not guarantee an execution order, and if handleFormData itself announces another event this may introduce additional complexity to the situation.

A more explicit solution would be to have the event handler for handleFormData explicitly announce the next event in the sequence and pass the event arguments to the next event so that any changes to the event object made in the first event are passed through to the second event. If handleFormData is only announced from one event handler, you can do this:

    <event-handler event="processForm" access="public">
        <announce event="handleFormData" />
    </event-handler>

    <event-handler event="handleFormData" access="private">
        ... handle the form data ...
        <announce event="displayResult" />
    </event-handler>

In real-world applications, it is likely that handleFormData is announced from more than one event handler and that displayResult is not always the next event to announce. Because of this fact, the next event to be announced must be dynamically selected. There are several ways to accomplish this, but the cleanest solution is probably to use <event-arg> to store the subsequent event name and then use an event filter to announce the next event. This technique is sometimes called continuation:

    <event-handler event="processForm" access="public">
        <event-arg name="continuationEvent" value="displayResult" />
        <announce event="handleFormData" />
    </event-handler>

    <event-handler event="handleFormData" access="private">
        ... handle the form data ...
        <filter name="continuation" />
    </event-handler>

The continuation filter simply announces the event specified by continuationEvent:

    <cfset arguments.eventContext.announceEvent(
        arguments.event.getArg("continuationEvent"),
        arguments.event.getArgs()
        ) />

Exception Handling

When an exception occurs in a filter or listener method, the framework catches the exception, creates a MachII.util.Exception object containing the details of the exception, and then announces the exception event that is defined in the <properties> section in the mach-ii.xml file. The handleException() Plugin Point can be used to provide custom processing when an exception occurs. An <event-mapping> can also be used to cause a specific event to be triggered when an exception occurs. For example:

    <event-handler event="someEvent" access="public">
        <event-mapping event="defaultException" mapping="someFilterException" />
        <filter name="someFilter" />
        <event-mapping event="defaultException" mapping="someListenerException" />
        <notify listener="someListener" method="someMethod" />
    </event-handler>

In this example, it is assumed that defaultException is the event defined by the exceptionEvent property in the <properties> section of the mach-ii.xml file. If an exception occurs in someFilter, the event someFilterException will be announced by the framework. If an exception occurs in someListener.someMethod(), the event someListenerException will be announced by the framework. If no event mapping is active, the defaultException event will be announced when an exception occurs. An event mapping is active from the point it is declared to the end of that event handler.

6. Designing Event Filters For Mach-II

This section discusses designing and using event filters. The first issue to consider is, given the desired functionality, if the correct object to use is an event filter or a plugin. After addressing that question briefly, this section examines some practical reasons for using event filters that may help improve the structure and flow of your application, and then examines larger issues such as form handling, validation, and security.

Event Filter or Plugin?

When first learning Mach-II it can be difficult to determine if you need an event filter or a plugin to achieve the desired functionality. By asking a few questions about the desired functionality it is relatively simple to determine which type of object will be more appropriate to a given situation:

  1. Does an operation need to be performed at the start or end of every single request?
  2. Does an operation need to be performed at the start or end of every single event handler?
  3. Does an operation need to be performed at the start or end of rendering every single view?

If the answer to any of these questions is "yes," the appropriate object to use is a plugin. Plugins will be discussed in a subsequent section.

If the above questions do not apply to a particular situation, consider the following questions:

  1. Does an operation need to be performed on the data provided to specific events?
  2. Does an operation need to be performed on the data returned by specific listener methods?
  3. Do certain event handlers need to be aborted conditionally?

If the answer to any of these questions is "yes," the appropriate object to use is an event filter.

The simplest way to think about event filters and plugins is that plugins are called on every event, while event filters are called only on the specific events in which they are declared. Plugins also provide a large number of plugin points allowing for operations to be performed at various discrete points during an event, while event filters are less granular in nature. In short, if some operation needs to be performed on every event, use a plugin, and if an operation only needs to be performed on specific events, use an event filter.

If the answer to all of the questions above is "no" but extending the application through the use of an event filter or plugin is desired, the rest of this section provides additional detail that can assist in the decision-making process.

Anatomy Of An Event Filter

An event filter is a component that provides a filterevent() method that is called by Mach-II, and this method is passed three arguments:

  1. The current event object
  2. The current eventContext object
  3. Optionally, a struct of name/value pairs from the <parameter> tags included in the filter invocation in mach-ii.xml

The filterevent() method can either return true, in which case processing of the event handler continues after the filterevent() method completes, or it can return false, in which case processing of the current event handler is terminated. If the event is terminated, processing continues with the next event in the queue unless the event queue is cleared by calling arguments.eventContext.clearEventQueue() in the filter. Usually when a filter returns false it will also announce a new event before doing so. If the filter clears the event queue then it must also announce a new event in order for the application to continue.

An event filter can define a configure() method if it needs to perform initialization. As with listeners, the configure() method of event filters is called automatically by the framework when the application is initialized. Event filters, like all other parts of the Mach-II framework, are stored in application scope so their instance variables are effectively application scope variables.

A minimal event filter CFC looks like this:

    <cfcomponent extends="MachII.framework.EventFilter">

        <cffunction name="configure" returntype="void" access="public" output="false">
            <!--- perform any initialization --->
        </cffunction>

        <cffunction name="filterEvent" returntype="boolean" access="public" output="false">
            <cfargument name="event" type="MachII.framework.Event" required="yes" />
            <cfargument name="eventContext" type="MachII.framework.EventContext" required="yes" />
            <cfargument name="paramArgs" type="struct" required="yes" />

            <!--- return false if you need to abort the current event --->
            <cfreturn true /> <!--- indicates success --->
        </cffunction>

    </cfcomponent>

The filter is declared in the <event-filters> section of the mach-ii.xml file as follows:

    <event-filter name="filterName" type="Path.To.YourFilter" />

In this example, filterName is the name you want to give the filter within the mach-ii.xml file and Path.To.YourFilter is the fully qualified component name for YourFilter.cfc. You may optionally specify parameters for the filter declaration:

    <event-filter name="filterName" type="Path.To.YourFilter">
        <parameters>
            <parameter name="param1" value="value1" />
            <parameter name="param2" value="value2" />
        </parameters>
    </event-filter>

This provides default parameter values for param1 and param2 (of value1 and value2 respectively). These can be accessed within the filter using the getParameter() method (see below).

Remember that filters must be called in each event handler in which they are to be used; this is in contrast to plugins, which are automatically called on every event and therefore do not need to be declared within each event handler. The syntax for calling a filter in an event handler is as follows:

    <filter name="filterName" />

This causes the filterevent() method to be called, and Mach-II passes the current event, the current event context, and an empty paramArgs struct to the filterevent() method.

You may optionally specify parameters to pass to the filter:

    <filter name="filterName">
        <parameter name="param1" value="newValue1" />
        <parameter name="param3" value="value3" />
    </filter>

This causes the filterevent() method to be called, and in this case Mach-II passes the filterevent() method the current event, the current event context, and a paramArgs struct containing two keys: param1 with a value of newValue1, and param3 with a value of value3. The intent is that parameter values passed in this way override any default parameter value specified in the declaration of the filter. This situation is typically managed with code similar to this:

    <cffunction name="filterEvent" returntype="boolean" access="public" output="false">
        <cfargument name="event" type="MachII.framework.Event" required="yes" />
        <cfargument name="eventContext" type="MachII.framework.EventContext" required="yes" />
        <cfargument name="paramArgs" type="struct" required="yes" />

        <cfset var param1 = getParameter("param1") />
        <cfset var param2 = getParameter("param2") />
        <cfset var param3 = getParameter("param3") />

        <!--- other var declarations --->
        <cfif structKeyExists(paramArgs,"param1")>
            <cfset param1 = paramArgs.param1 />
        </cfif>

        <cfif structKeyExists(paramArgs,"param2")>
            <cfset param2 = paramArgs.param2 />
        </cfif>

        <cfif structKeyExists(paramArgs,"param3")>
            <cfset param3 = paramArgs.param3 />
        </cfif>

        <!--- perform filter processing --->
        <!--- return false if you need to abort the current event --->
        <cfreturn true /> <!--- indicates success --->
    </cffunction>

An event filter has access to both the current event and the current event context, so it is able to affect the logical flow of an application by manipulating the event context. For example, when an event filter aborts the current event by returning false, it generally needs to announce another event for the framework to execute, and it may also decide to clear any events that are waiting in the event queue. By convention, event filters that may abort processing and clear the event queue typically have a parameter that specifies the next event to announce, as well as a parameter that specifies whether or not to clear the event queue:

    <cffunction name="filterEvent" returntype="boolean" access="public" output="false">
        <cfargument name="event" type="MachII.framework.Event" required="yes" />
        <cfargument name="eventContext" type="MachII.framework.EventContext" required="yes" />
        <cfargument name="paramArgs" type="struct" required="yes" />

        <cfset var invalidEvent = getParameter("invalidEvent") />
        <cfset var clearEventQueue = getParameter("clearEventQueue") />

        <!--- other var declarations --->
        <cfif structKeyExists(paramArgs,"invalidEvent")>
            <cfset invalidEvent = paramArgs.invalidEvent />
        </cfif>

        <cfif structKeyExists(paramArgs,"clearEventQueue")>
            <cfset clearEventQueue = paramArgs.clearEventQueue />
        </cfif>

        <cfif someCondition>
            <!--- note: clearEventQueue parameter is really a string --->
            <cfif clearEventQueue is "true">
                <cfset arguments.eventContext.clearEventQueue() />
            </cfif>

            <!--- pass current event's arguments into the new event: --->
            <cfset arguments.eventContext.announceEvent(invalidEvent,arguments.event.getArgs()) />

            <cfreturn false />
        </cfif>

        <cfreturn true />
    </cffunction>

Data Validation

When user data enters an application, either as URL scope or form scope variables, performing some sort of validation on this data is generally required. This validation may be as simple as checking that certain variables have been provided or it may be something substantially more complex.

The Mach-II framework provides the RequiredFieldsFilter event filter that can be used to check if a specified list of URL or form scope variables are present, and if one or more of the required fields is missing, this filter announces a specified event and returns false to abort the current event. This filter takes two parameters:

  1. requiredFields: a comma-separated list of field names that are to be checked
  2. invalidEvent: the event to announce if any fields are missing

If any fields are missing, the event that is announced has the same arguments as the current event plus the following additional arguments:

  1. message: an error message (in English)
  2. missingFields: a comma-separated list of field names that are missing

For more complex web form handling, a bean should be created to encapsulate the web form data, and the <event-bean> command can be used to create the bean object from the event arguments. In addition, the bean may provide a validate() method that returns a boolean.

The ValidateFormObject filter takes two required and one optional parameter:

  1. formObjectName: the name of the event argument on which to invoke validate()
  2. invalidEvent: the event to announce if validate() returns false
  3. clearEventQueue: an optional boolean that indicates whether or not to clear the event queue before announcing invalidEvent

If validate() returns false, the filter also returns false after optionally clearing the event queue and then announcing the specified event with the same arguments as the current event, and it also adds the an additional argument called formObjectName, which is the name passed into the filter.

It is up to the specified event handler to determine how to deal with reporting the validation failure, which is likely to involve additional calls to the data object and, therefore, additional event filter invocations to manage that interaction.

Default parameters for the ValidateFormObject event filter may be specified as follows:

    <event-filter name="barValidator"  type="Path.To.ValidateFormObject">
        <parameters>
            <parameter name="formObjectName" value="bar" />
            <parameter name="invalidEvent" value="formHasInvalidBar" />
        </parameters>
    </event-filter>

    <event-filter name="fooValidator" type="Path.To.ValidateFormObject">
        <parameters>
            <parameter name="formObjectName" value="foo" />
            <parameter name="invalidEvent" value="formHasInvalidFoo" />
            <parameter name="clearEventQueue" value="true" />
        </parameters>
    </event-filter>

These event filters can then be used without needing to specify the parameters each time, or the default values may be overridden:

    <event-handler event="someEvent">
        <event-bean name="bar" type="my.model.bar" />

        <!--- this filter uses the default parameters --->
        <filter name="barValidator" />
    </event-handler>

    <event-handler event="someOtherEvent">
        <event-bean name="foo" type="my.model.foo" />

        <!--- this filter overrides the default parameters --->
        <filter name="fooValidator">
            <parameter name="invalidEvent" value="warnAboutBadFoo" />
            <parameter name="clearEventQueue" value="false" />
        </filter>
    </event-handler>

Security

When user authorization needs to be performed on certain events, an event filter is probably the simplest way to achieve this. The Mach-II framework provides the PermissionsFilter event filter to support simple permission-based authorization checking. This filter checks that the current user has all of the necessary permissions (specified as a comma-separated list) and if they do not, it announces the event specified after optionally clearing the event queue. See the comments in the source code for more detail.

Extending the PermissionsFilter component to implement a different security model would be relatively trivial, and would involve overriding the getUserPermissions() method to return a comma-separated list of user permissions. If the default inclusive permission checking behavior is not desired, or the user permissions for a particular application are more complex than a comma-separated list, the validatePermissions() could also be overridden to implement a different type of security check.

If the security needs of an application go beyond this rather simple permission type, a new security event filter would need to be written.

7. Designing Plugins For Mach-II

This section discusses the design and use of plugins in Mach-II applications. For information about determining if an event filter or a plugin will better suit a particular need, see the discussion in the section concerning event filters above.

Anatomy Of A Plugin

A plugin is a component that provides methods that are called with the current eventContext (which is an instance of the MachII.framework.EventContext object), and offers access to perform actions at various points during the request lifecycle as follows:

  1. preProcess(): called at the start of each request. At this point the eventContext has the current event in the event queue but getCurrentEvent() will not yet return that event; see “Processing The First Event In A Request” below for additional details.
  2. preEvent(): called for each event immediately prior to execution of the relevant <event-handler> tag. At this point the eventContext contains the current event, i.e., the event that is about to be handled.
  3. preView(): called for each view immediately prior to execution of the relevant <view-page> tag. At this point the eventContext contains the current event, i.e., the event that is currently being executed.
  4. postView(): called for each view immediately after execution of the relevant <view-page> tag. At this point the eventContext contains the current event, i.e., the event that is currently being executed.
  5. postEvent(): called for each event immediately after execution of the relevant <event-handler> tag. At this point the eventContext contains the current event, i.e., the event that has just been handled and concluded.
  6. postProcess(): called at the end of each request. At this point the eventContext no longer contains an event.
  7. handleException(): called whenever an exception is thrown back to the framework. When an exception occurs, the eventContext contains the current event if there is one. This plugin method is also passed an exception object of type MachII.util.Exception.

A plugin can define a configure() method if it needs to perform initialization. As with listeners and filters, the configure() method is called automatically by the framework when the application is initialized. Plugins, like all other parts of the Mach-II framework, are stored in application scope so their instance variables are effectively application scope variables.

A minimal plugin CFC might look like this:

    <cfcomponent extends="MachII.framework.Plugin">
        <cffunction name="configure" returntype="void" access="public" output="false">
            <!--- perform any initialization --->
        </cffunction>

        <cffunction name="preEvent" returntype="void" access="public" output="false">
            <cfargument name="eventContext" type="MachII.framework.EventContext" required="yes" />
            <!--- perform processing prior to every event being handled --->
        </cffunction>
    </cfcomponent>

This plugin overrides only the preEvent() method, but it could override any of the seven methods contained in the base MachII.framework.Plugin object (see the methods listed above).

Plugins are declared in the <plugins> section of the mach-ii.xml file as follows:

    <plugin name="pluginName" type="Path.To.YourPlugin" />

As with event filters, parameters may optionally be specified in the plugin declaration:

    <plugin name="pluginName" type="Path.To.YourPlugin">
        <parameters>
            <parameter name="param1" value="value1" />
            <parameter name="param2" value="value2" />
        </parameters>
    </plugin>

This provides default parameter values for param1 and param2 (of value1 and value2 respectively). These can be accessed within the plugin using the getParameter() method, e.g., getParameter("param1").

The handleException() Plugin Point

The framework itself can handle all exceptions by using the exception event and a single event handler. For many applications this will be perfectly acceptable. Custom exception handling may also be added to an application by implementing the handleException() method in a plugin. This plugin point is executed after an exception is encountered, but before the exception event is announced and handled by the framework itself or any other components that may handle the exception. After executing this plugin point, the event queue is cleared and the specified exceptionEvent (from the <properties> section of mach-ii.xml) is announced and handled.

Exceptions should not be thrown from handleException(), and abortEvent() should not be called, because both these operations will lead to an unhandled exception that will be displayed to the end user. Furthermore, a new event cannot be announced since the event queue is cleared after handleException() has been executed. However, information may be added to the current event object, and this information can then be retrieved in the exception event handler as follows:

    <cfif arguments.eventContext.hasCurrentEvent()>
        <cfset arguments.eventContext.getCurrentEvent().setArg("argName",argValue) />
    </cfif>

This will set the event argument argName to the value argValue if an event was defined when the exception was encountered.

Inside the exception event handler, the current event is the exception event itself, which contains the following arguments:

  • exception: the MachII.util.Exception object containing details of the original exception
  • exceptionEvent: the MachII.framework.Event object that was being handled when the exception was thrown. This is only present if there was a current event defined when the exception occurred.

An <event-mapping> may also be used to provide more fine-grained control over Exception Handling in event handlers.

Processing The First Event In A Request

Since a current event is not set by the time the preProcess() method of a plugin is invoked, it is might not be obvious how to process just the first event in each request:

    <cffunction name="preProcess" returntype="void">
        <cfargument name="eventContext" type="MachII.framework.EventContext" />

        <!--- peek at the first event in the queue: --->
        <cfset var firstEvent = arguments.eventContext.getNextEvent() />

        <!--- ...process firstEvent here... --->
    </cffunction>
⚠️ **GitHub.com Fallback** ⚠️