Welcome!

ColdFusion Authors: Maureen O'Gara, Hovhannes Avoyan, Yakov Fain, Pat Romanski, Liz McMillan

Related Topics: ColdFusion

ColdFusion: Article

Creating Maintainable Web Sites

Creating Maintainable Web Sites

"It doesn't matter to me that you use my methodology," said the Fusebox expert sitting across from me at lunch. "What matters is that you have a methodology." And I thought back to my early experiences with ColdFusion...

I developed my first ColdFusion application by the seat of my pants. I opened the manual, looked through the sample applications, and got started. On looking back, I was surprised that the application performed relatively well and was somewhat maintainable. I got lucky.

On my next ColdFusion application I was part of a team. We dove in and started programming. The application scope grew, and we added more code to support the new specifications. Eventually the amount of time devoted to developing new features was dwarfed by the time required to make the new code consistent with the old code. Our application became unmaintainable, and after about a year it collapsed from its own weight.

As I continued developing, and discussed application design with other CFers, I learned more about what worked and what didn't, and came up with standard designs to improve both performance and maintainability. And as I documented these approaches, I was creating what are known as "patterns": consistent, explainable relationships between software components that improve with usage and evolve over time.

I wasn't alone. The challenge of creating consistent, maintainable code is universal to all software development, and there are thousands of articles and books describing various solutions. Allaire, and now Macromedia, historically haven't enforced a single application architecture. Instead, they've provided the tools and allowed developers to come up with their own architectural solutions. As a result, various communities and companies have created their own patterns (Fusebox, CFObjects, and Allaire's own Spectra all come to mind).

ColdFusion MX offers new tools for creating a clean, maintainable application architecture. It still doesn't enforce a single solution, but it now provides the tools required to adapt some of the patterns that are popular in object-oriented languages like Java. The most important new construct is the ColdFusion component, a file that, in the words of the ColdFusion documentation, can "encapsulate application functionality and provide a standard interface for client access to that functionality."

This article describes possible ColdFusion adaptations of a common software design pattern known as Model-View-Controller, or "MVC", and explores how to implement this pattern in CFMX while leveraging the strengths of CFCs.

This article is investigation, not prescription. I do not intend to present a complete, mature application architecture to compete with existing systems. Instead, take these as thoughts that occurred to me over the last few months while developing my first "from scratch" CFMX application.

A Maintenance Nightmare
Consider a typical ColdFusion page that has been developed by a beginning programmer. The page's purpose is to query a database table and present its contents. The original ColdFusion page would execute these steps:

  • Query the database.
  • Present the data in an HTML table.
This is fine, as long as the programmer wants to present the data only one way. But inevitably the specifications expand. The programmer is asked to support not only a typical Web browser, but also wireless devices such as mobile phones and PDAs. So the original page is duplicated twice, and the output code is modified to support the new devices. The developer now has three pages, each of which includes a copy of the database query (see Figure 1).

Now the database administrator informs the programmer of a change to the database table structure; the nightmare begins. The programmer has to track down all the queries that address that table and update them. Multiply this out to a large application with thousands of pages and queries and the programmer hopes for the layoffs to begin.

Model-View-Controller
MVC was originally developed in Smalltalk as a methodology for interpreting and reacting to end-user input. It has since been adapted in the world of Java in Swing (a graphical user interface component kit) and in J2EE application servers such as JRun, WebSphere, and WebLogic as a system for developing maintainable Web applications. (For detailed information on the J2EE implementation of MVC, see http://java.sun.com/blueprints/patterns/ j2ee_patterns/model_view_controller/.)

The MVC pattern separates application logic, content, and presentation, resulting in more maintainable and reusable software components. There are three primary components in an MVC implementation:

  • Controller: Receives and interprets user input. In this CFMX adaptation this is a ColdFusion page that receives and reacts to the user request.

  • Model: The interface to the application's data. It encapsulates queries and other server-side operations, and manages state and data caching. In this CFMX adaptation the Model is implemented as a CFC.

  • View: Creates a text and/or graphic response for the end user. Its jobs include rendering of data models and presenting user interfaces for additional user input. In CFMX the View can be an included ColdFusion page or a custom tag.

    The MVC pattern can be visualized as in Figure 2.

    Implementing MVC in CFMX requires at least three files. The Controller page (user_list.cfm, see Listing 1) is called from the browser. The Model (user_db.cfc, see Listing 2) is a ColdFusion component that's designed to be instantiated and held in memory for some amount of time. In this implementation two CFCs have a parent-child inheritance model. The CFC in Listing 2 extends and inherits the functionality of a "parent" CFC in Listing 3. The View is an included ColdFusion page (user_list. cfm, see Listing 4) that contains nested Views (view_row.cfm, see Listing 5).

    The MVC Conversation
    The end user initiates a conversation by creating user input. In a Web application the input might be a click on a hyperlink or a submit button.

    The Controller receives the resulting request from the browser and executes at least two steps. First it creates an instance of the Model CFC in the Request scope. Next it examines the URL scope for a "sort" argument; if it exists, it passes it to the Model, modifying the Model's current state. Finally, it calls a View that's designed to output the contents of the Model. The Controller code looks like this:

    <cfset request.data=createobject("component", "user_db")>
    <cfif isdefined("url.sort")>
    <cfset request.data.setSortOrder(url.sort)>
    </cfif>
    <cfinclude template="user_list_view.cfm">
    The Model CFC is an instance of an object with properties and methods. It's designed to encapsulate and provide access to information in the database query, while protecting the data consumer (the View) from having to know anything about the data's source or structure. Figure 3 shows the output of the CFC when I browse it directly. I see the component's method names, and can tell which of the methods were inherited from the parent CFC.

    When the component is first instantiated, it runs its parent CFC's constructor code (the code prior to the method definitions) and creates a locally owned variable named "this.query" as a nonquery type. When data is first requested from the object, the component sees that the query hasn't been created and runs an initialization method:

    <cfif not isquery(this.query)>
    <cfset this.init()>
    </cfif>
    The init() method in the child CFC actually executes the query:

    <cfquery name="this.query"
    datasource="myDSN">
    SELECT * from users
    <cfif this.sortorder neq "">
    ORDER BY #this.sortorder#
    </cfif>
    </cfquery>
    In the listing, you'll see that the query has been wrapped in a <cftry> block. If the query fails (as it will on your system, as you don't have my database), the code forks into a routine to manually create a query object with the expected fields (see Listing 6). This is an important lesson in encapsulation. The Model CFC is in charge of getting or creating the data. The View and Controller know only that data is available, but don't know where it came from.

    The View page is included only when the CFC instance has been placed in the Request scope. The View's first job is to create a reference to the CFC instance:

    <cfset data=request.data>

    The View then outputs an HTML table presenting the data to the user. Notice that it doesn't use a conventional <cfoutput> loop with a query attribute. Instead, it uses the CFC's next() and getValue() methods to retrieve data for output:

    <cfloop condition="data.next()">
    <cfoutput>
    <tr>
    <td>#data.getValue("user_name")#</td>
    <td>#data.getValue("user_city")#</td>
    </tr>
    </cfoutput>
    </cfloop>
    Why the change in syntax? One of the Model's most important responsibilities is to hide the structure and source of the data from the View. Remember that your system can be thrown into chaos by a simple change in data structure. What would happen in an even more extreme situation, where the data no longer comes from a database but is now retrieved from LDAP, XML, comma-delimited text, or a Web service? The Model's job is to get the data and serve it back, regardless of source or structure. The CFC's interface serves that purpose. The View doesn't know it's operating on a ColdFusion query; it knows only that the Model object has the data and knows how to provide it.

    Now let's see what happens when the specifications expand as before. When the developer is asked to support multiple devices, it's easy to develop additional View files and call them dynamically from the Controller:

    <!--- Assumes a session.
    device variable --->
    <cfinclude template="user_list_#session.device#.cfm">
    The developer can add as many View pages as necessary. Each of the Views is maintained independently, so fixing an output bug doesn't impact the Model or Controller code. The Controller is responsible for choosing which Model and which View to use; again, changes in this logic don't impact the other two parts of the model.

    If the structure or source of the data changes, the developer modifies the Model CFC. The Model is maintained independently, and the Controller and View files are left alone. As long as the data coming from the Model matches the View's expectations, the Model's internal implementation can vary as needed.

    This independence of the View from the Model, and vice versa, is known as "loose coupling." They are designed to work together, but can also work with other partner components as long as they agree on the syntax of their conversation.

    Views Within Views
    The View is the outward presentation to the user. A single View can represent the entire page, but it's also common to have small pieces of visual presentation nested within the larger view.

    A good example of this is the common CF practice of including header.cfm and footer.cfm files to give your application a common look and feel. Both the header and footer files create browser output, so they are Views. They are then nested within a "master" view. Figure 4 is a visualization of this architecture.

    Each part of the nested View outputs its result directly to the HTTP response. To get data, each converses directly with the Model instance stored in the Request scope (see sidebar). The use of nested Views is a good reason to place data in the Request scope rather than passing the CFC instance directly from the Controller into the top View, and so on down the calling chain. Since all Views get their data from the same place, they're independent of each other, and modifying one doesn't have a negative effect on any other.

    In this final version of the View, I've separated the section that outputs the HTML table row into its own nested View. Listing 5 is a custom tag that expects a comma-delimited list of field names. This View connects back to the Request-scoped Model, and retrieves one field value at a time:

    <cfset data=request.data>
    <tr>
    <cfloop list="#attributes.fieldlist#"
    index="fieldname">
    <td>#data.getValue(fieldname)#</td>
    </cfloop>
    </tr>
    A single nested View can now be used throughout the application whenever I want to output a table row with dynamic data. Changing the look and feel of the entire application can be accomplished with modifications to a single file.

    SIDEBAR
    Passing the Model Object

    There isn't a single standard for how the Model object is passed to the View. In J2EE, where JSP pages are usually used as the View, it's common to place the Model in the Request scope, as it isn't possible to pass arguments to JSP files. In ColdFusion MX you can use custom tags as Views, so it would be valid (and perhaps preferable) to pass the CFC instance to the View as a custom tag attribute.

    Each approach has its advantages. If the CFC instance is Request-scoped, each nested View isn't dependent on getting its data from its parent View, so you achieve more independence. But what if each View is supposed to get a different version of the Model? In this case it's probably better to pass the specific Model instance as an attribute, achieving better control.

    More About Models
    Macromedia recommends the use of CFCs in CFMX whenever you need functionality that's strictly server-side. Database queries, file management functions, communication with LDAP servers, and parsing of XML files are all examples of functions that manage data, not presentation. A CFC is ideally suited for use as a Model.

    In the MVC pattern the Model is the most visible to, and the least aware of, the other members of the pattern. By "most visible" I mean that it can be seen and used by a Controller or a View. By "least aware" I mean that it doesn't need to know who is calling it.

    In some MVC implementations the Model is able to notify Views of changes in state. In a ColdFusion implementation the View is short-lived (it doesn't stay in a persistent scope as the Model does, or as a View would in a persistent visual application). So in ColdFusion it only makes sense for the View to make requests of the Model and not the other way around.

    Imagine that you've designed your MVC application from the beginning assuming that your user data is in a database table. Now you're told that the data will be moved to an LDAP (Lightweight Directory Access Protocol) server. Knowledge of the source and structure of your user information is programmed solely in the CFC and is hidden from the rest of your application. In object-oriented terms the data has been "encapsulated."

    All required changes to your application will be localized to the CFC. As long as the syntax of the CFC method remains stable, the other members of the pattern (the Controllers and Views) don't have to be modified at all. Again, maintainability is significantly improved.

    It's also possible to take advantage of CFC inheritance to create a clean, maintainable architecture. Notice that the file user_db.cfc (see Listing 2) includes only the init() method. All other methods are inherited from a "super" CFC (see Listing 3). The methods in this class are common to all of my data model CFCs. It would also be possible to create sibling CFCs for reading XML files or translating Web service calls. As long as they all deliver their data to the View with the same API, the Views don't need to change.

    Caching and State
    State is a big deal in Web applications. Whether you need to store user information for site personalization or improve performance with intelligent data caching, ColdFusion has always made it very easy to create and read persistent data using the Session and Application scopes.

    Imagine that I want to cache the "users" table in server memory. Whenever a user record is added, updated, or deleted, the cached version should be refreshed from the database.

    In a typical ColdFusion application that doesn't use components, the logic and variable names would be scattered among multiple ColdFusion pages. A CFC, however, can be created in the Application or Session scope, and can store data and logic in its own properties and methods.

    In my Application.cfm page I might have this code:

    <cfif not structkeyexists(application, "userdata")>
    <cfset application.userinfo=createobject("component", "user")>
    </cfif>

    As before, in the constructor code I could initialize a variable named "query" as a member of the component instance using the "this" prefix:

    <cfset this.query="">

    In the function that creates the query object of all users, I would first check to see whether the variable is a true query object. If it isn't a variable of type "query," I execute <cfquery> to refresh the data:

    <cffunction name="init()">
    <cfif not isquery(this.query)>
    <cfquery name="this.query">
    SELECT * from users
    </cfquery>
    </cfif>
    </cffunction>
    If the CFC instance has been cached in the Application scope, the data is now available to all users of the Application. The first use of the init() method by any user will cause the query to be created. Subsequent users will get the query from memory.

    The cached data can be cleared within the CFC whenever necessary. For instance, this CFC function is used to update user records:

    <cffunction name="updateUser">
    <cfargument name="id" type="numeric">
    <cfargument name="name" type="string">
    <cfquery datasource="myDSN">
    UPDATE users
    SET user_name='#arguments.name#'
    WHERE user_id=#arguments.id#
    </cfquery>
    <cfset this.query="">
    </cffunction>
    Resetting the variable "this.query" to a string forces the query to be refreshed the next time the data is requested. This logic would be repeated whenever the data in the database table has been changed.

    Caching logic is now restricted to the CFC. External users of the component simply ask for the data. It's up to the Model to determine whether the data should be read from database or memory. No matter how many times the data is called from various parts of the application, if the business logic or data storage mechanism changes, the programming changes will again be localized to the CFC and won't have any effect on the Views that make use of the data.

    Conclusion
    Every software design choice has both benefits and consequences. Adoption of MVC-style programming makes your site more maintainable, but takes more initial time and thought than simply throwing a few ColdFusion pages together.

    Also, any new design pattern should be load-tested early in the development process to ensure that the extra layers of code don't end up crippling the application's performance. But the first time you make a significant change to your application and find you can do it in just a few minutes without fear of collapsing the whole house of cards, the exercise pays off huge dividends.

    In this article I've described some of my own experiments with the new CFMX toolset. As I mentioned at the beginning, I'm just beginning to explore the power and flexibility of the CFC programming model. Try it out and let me know what you learn.

    Resources

  • ColdFusion components: www.macromedia.com/desdev/mx/coldfusion/ articles/intro_cfcs.html
  • Alur, D., Crupi, J., and Malks, D. (2001). Core J2EE Patterns: Best Practices and Design Strategies. Prentice Hall.
  • J2EE Design Patterns: http://java.sun.com/blueprints/patterns/ j2ee_patterns/model_view_controller/
  • Software design patterns: Design Patterns, Addison Wesley, 1995; www.macromedia.com/desdev/articles/design_patterns.html
  • Comments (1) View Comments

    Share your thoughts on this story.

    Add your comment
    You must be signed in to add a comment. Sign-in | Register

    In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.


    Most Recent Comments
    11/15/02 03:00:00 AM EST

    Please arrange to send the basic idea about CF technology and its applications therein.

    Regards,

    Dileep Rawat