View comments | RSS feed

ColdFusion MX Coding Guidelines - Structure: Application, Component, Tag etc

Release 3.2 (4/14/2005)

« Style: Naming, Comments & Layout | Contents | Good Practice »

Printable Version

Structure: Application, Component, Tag etc

This section provides guidelines on how to structure your code and take advantage of the power of ColdFusion Components, to create well-designed, maintainable ColdFusion applications.

In general, MVC - Model-View-Controller - is a good, basic design pattern to use as a guideline for designing your application. It helps you focus on separating logic from presentation as well as refining the logic to separate the pure business model from the application workflow and logic.

Construct as much of the application logic as possible using CFCs so that you can take advantage of the encapsulation and type safety that they offer, as well as providing better options for reuse. Structure the CFCs to be as independent of each other as possible and as self-contained as possible (loose coupling and high coherence respectively). In particular, structure CFCs so that environmental awareness (e.g., use of shared scopes) is minimized, using design patterns such as Session Façade.

Mach II and Fusebox 4.1 are both well-designed frameworks for building MVC-based applications. See the Mach II Development Guide for more information about object-oriented design and best practices.

Basic Modularity

ColdFusion components, custom tags, user-defined functions, tag libraries and included files should be used if their usage will satisfy any of the following three conditions:

  1. Reusability
  2. Readability
  3. Organization

That means that all but the very simplest ColdFusion page should take advantage of CFCs and / or custom tags. Components should be used in preference to custom tags (for encapsulation and type safety reasons) although in certain situations, e.g., where part of a page has a natural start and end that needs to be managed as a single unit, custom tags can be more idiomatic.

A good example is the use of mmlf:renderpage (and mmlf:rnav) to wrap the body (and right navigation module) of a page and render it using standard header, footer and style sheets. Another good example of when it is natural to use a custom tag is the mmlf:ssi tag, used to perform 'server-side includes' of HTML fragments from the web servers. Both of these uses would be harder to achieve with CFCs and would be less intuitive to use.

If performance is critical or external integration requires it, Java classes may be used, or tag libraries with cfimport in preference to CFX tags.

File Structure

Each file should begin with an appropriate comment - see Style: Comments.

Component File Structure

CFCs should be structured as follows:

<!--- prolog comment --->
<cfcomponent ...>
    ...pseudo-constructor initialization (if any)...
    ...public methods (with init() first)...
    ...package methods (if any)...
    ...private methods...
</cfcomponent>

The use of pseudo-constructor initialization should be kept to a minimum and instead an init() method should be used to initialize the component - see Good Practice: Constructors. The public methods are the most important part of the component so they should be the first thing someone reads. The public methods should be followed by any access="package" methods and then any access="private" methods. Users of a component should not have to read as far as the private methods in order to figure out how to use your component - well-chosen names (and good comments) for the public methods should be sufficient.

.cfm File Structure

Even within a single file, separate logic from presentation as much as possible. If logic and presentation code cannot be physically separated (into different files), then try to structure files along the following lines:

<!--- prolog comment --->
<cfsilent>
...CFML logic...
</cfsilent>

<cfoutput>
...HTML generation...
</cfoutput>

Note: cfsilent suppresses all HTML output. This should not be a problem if logic and presentation code are properly separated. An Alternative is to use cfsetting as follows:

<cfsetting enablecfoutputonly="yes" />
<!--- prolog comment --->
...CFML logic...
 
<cfoutput>

...HTML generation...
</cfoutput>
<cfsetting enablecfoutputonly="no" />

Note: You should have both the yes and the no versions of the tag present and in the same file (to avoid creating hard-to-debug problems with unexpected output or missing output).

Directory Structure For Applications

All ColdFusion code should live in a directory tree outside the install area for ColdFusion MX / JRun. On most servers, the root for that directory tree is /data/www/appserver/cfmx/ and we'll refer to that as the {cfmxroot} below:

{cfmxroot}
    wwwroot/           » web-accessible .cfm pages and .cfc Web Services
    extensions/
        components/    » tree for .cfc files
        customtags/    » tree for .cfm custom tags
        includes/      » tree for include files
    config/            » tree for configuration files

This implies that we have two Custom Tag Paths set up in the CFMX Administrator:

{cfmxroot}/extensions/components/
{cfmxroot}/extensions/customtags/

We also have mappings for the root of the includes tree (for cfinclude) and the custom tags tree (for cfimport):

/cfinclude             » {cfmxroot}/extensions/includes/
/customtags            » {cfmxroot}/extensions/customtags/

For Mach II development, we also have a mapping for including the core file:

/MachII                » {cfmxroot}/extensions/components/MachII/

For Fusebox development, you could put the Fusebox core files (for 4.1) in a directory underneath the includes tree, such as:

{cfmxroot}/extensions/includes/fusebox4

and then have a mapping for including the core file:

/fusebox4              » {cfmxroot}/extensions/includes/fusebox4 

See Site-wide Variables for information about a /environment mapping.

The pieces of each logical application live in an application-specific directory in each of the trees above, e.g., code for the Exchange application lives in:

{cfmxroot}/wwwroot/exchange/
{cfmxroot}/extensions/components/exchange/
{cfmxroot}/extensions/customtags/exchange/
{cfmxroot}/extensions/includes/exchange/

Any Java libraries required should live in JRun's servers/lib/ directory (although, perhaps a little confusingly, in our build system we still have ant deploy to WEB-INF/lib/ as if it were part of CFMX and then the build system moves the files to the right place!).

URL Usage

URLs must not hardcode server names such as www-staging, www.macromedia.com, etc. These variables will be different on staging, QA, integration and production and should be handled using Site-wide Variables (in the next section).

URLs form the API to our web site. We have to live with them forever. Take the time to get them right, design your query string parameters carefully, be consistent, etc. Here are some preliminary specs.

Site-wide Variables

Some attributes within web applications depend on the server environment and will differ between development, staging, integration and production. The recommended approach for such attributes is to provide their values as request scope variables that are set as part of Application.cfm or Application.cfc. However, Application.cfm and Application.cfc should be deployable files that are independent of the server environment so the variables should be set in a server-specific include file (i.e., a file that has the same name but different content on every server). This way, Application.cfm and Application.cfc will be standard, deployable source files that are identical in each of the four environments while the included file, or database table contents, are considered part of the server configuration itself.

The server-specific include file will be called sitewideconstants.cfm and will exist in directories for development, staging, integration and production. The root Application.cfm will include the file as follows:

<cfinclude template="/environment/sitewideconstants.cfm" />

In each environment, /environment will be mapped to the appropriate directory, outside the document root. For the most part, this is the target config directory created automatically by the build system ({cfmxroot}/config/target/). Similarly, /cfinclude will be mapped to the include file root ({cfmxroot}/extensions/includes/). The build system automatically creates a serverspecific.cfm configuration file that contains:

The primary Application.cfm or Application.cfc file will also include that server-specific file, as follows:

<cfinclude template="/environment/serverspecific.cfm" />

The CVS source code control tree looks like this:

/source/
    docroot/                Web Server Document Root
        swf/                .swf
    java/                   Java Source Root
        com/
            macromedia/
                eai/        com.macromedia.eai package
                ...
        config/
            development/    Development configuration for Java apps
                application/
                            Specific configuration for application
            staging/
                application/
            integration/
                application/
            production/
                application/
    neo_root/               Application Server Root
        config/
            development/    Development
            staging/        Staging
            integration/    Integration
            production/     Production
            target/         Deployed directory (/environment)
        extensions/         Non-URL accessible CF files
            components/     ColdFusion Components
				MachII/		Mach II framework (/MachII)
            customtags/		Custom Tags (/customtags)
            includes/       Included Files (/cfinclude)
                fusebox4/   Fusebox 4 framewoek (/fusebox4)
        wwwroot/            .cfm Document Root

In addition to the source code tree shown above, the following directories will also exist in the repository, which is documented in full in the Dylan65 CVS Layout.

source/
    database/               DDL and other files
    docs/                   Engineering Document Tree
    infrastructure/         Non-Web Configuration (e.g., messaging)
    orientation/            Orientation Projects
    qa/                     QA (e.g., test harnesses)
    release_eng/            Release Engineering (e.g., scripts)
    scratch/                Scratchpad for anything!

The general assumption is that global include files of all sorts will live outside the document root, in appropriate directory structures, with suitable logical names for mappings.

Application.cfm / Application.cfc

There will be a root Application.cfm or Application.cfc file that provides all the site-wide core services such as application and session settings, site-wide constants, form / URL encoding machinery etc. Values will all be set in request scope rather than variables scope to ensure they are available inside custom tags etc.

If you use a root Application.cfm file, each "application" on the site will also have an Application.cfm file containing application-specific code that starts by including the root Application.cfm. Alternatively, each "application" on the site may have an Application.cfc file instead that includes the root Application.cfm file into onRequestStart().

If you use a root Application.cfc file, each "application" on the site may either have an independent Application.cfm file (containing all the appropriate definitions) or an Application.cfc file that extends the root Application CFC (but you'll need a mapping to reference the root file). Since Application.cfc is such a new feature, expect best practices around it to evolve over time.

Each "application" will also typically have an include file, applicationvariables.cfm, that defines the application-specific variables, again in request scope. This will also be included by the application-specific Application.cfm file (or by the onRequestStart() method of Application.cfc). The variables should be those that might be needed by other applications that need to take advantage of the services of this application, e.g., the membership application might define an include file with LDAP and data source settings, for use by the store and exchange applications.

The applicationvariables.cfm file belongs in:

{cfmxroot}/extensions/includes/{appname}/

/Application.cfm:

/{appname}/Application.cfm:

Exception Strategy

ColdFusion MX allows exceptions to have a pseudo-hierarchy by allowing cfcatch to specify a type= attribute that has a compound dot-separated name, like a component name, that will catch exceptions that have that type or a more specific type, e.g.,

<!--- catches feature.subfeature.item: --->
<cfcatch type="feature.subfeature.item"> .. </cfcatch>
<!--- catches feature.subfeature.{anything}: --->
<cfcatch type="feature.subfeature"> .. </cfcatch>
<!--- catches feature.{anything}: --->
<cfcatch type="feature"> .. </cfcatch>

Each 'application' (or feature) should define and publish its exception hierarchy. Most hierarchies will probably only have feature and item, e.g., Membership.NO_SUCH_ATTRIBUTE. The intent is that the feature.item (or feature.subfeature.item) type should entirely specify what exception has occurred.

Each application should throw fully-qualified exception types (feature.item or feature.subfeature.item) and a descriptive message that says - in English - which component / method threw the exception and a description of the problem, e.g.,

<cfset var msg = "MembershipAdminMgr.setAttributeName() :: " &
        "no attribute could be found with the given attribute name">
<cfthrow type="Membership.NO_SUCH_ATTRIBUTE" message="#msg#">

The code that invokes that application should catch the exceptions using fully-qualified types that it can handle, followed by feature.subfeature or feature for reporting more generic exceptions, e.g.,

<cfcatch type="Membership.NO_SUCH_ATTRIBUTE">
    <!--- handle missing attribute error --->
</cfcatch>
<cfcatch type="Membership">
    <!--- handle general membership failure --->
</cfcatch>
<cfcatch type="application">
    <!--- handle general application failure --->
</cfcatch>
...

Debugging

This section discusses some common debugging techniques.

Explicit Debugging Code

This involves adding specific code to an application to output additional information when a specific debugging mode is enabled. Typically such debug code would output to the HTML stream or write to a log file (using <cflog>). The debugging mode is typically enabled through a URL parameter. At Macromedia we have standardized on debug=yes as a URL parameter and we have a custom tag (getrequestsettings) that is called in the Application.cfm file to create a structure in request scope that includes debugging information (and locale, language etc). The custom tag ensures that debugging cannot be enabled in a production environment.

Debugging output within a page is structured as follows:

if ( request.settings.debug ) {
    // output debugging information
}

Do not use URL.debug directly.

Implicit Debugging

ColdFusion also has powerful built-in debugging features but there are some caveats to be aware of. If you use ColdFusion Components extensively, then you need to ensure that Report Execution Times is disabled in the ColdFusion Administrator since it adds a noticeable performance overhead. If you have the option of using localhost development, it's a good idea to restrict your debugging efforts to that environment rather than inflicting debugging on anyone else sharing your development server!

Shared Scopes, Sessions, Clustering & Locking

This section discusses the considerations behind use of shared scopes (application, server, session etc), how sessions are managed, how we use clustering and what to do about locking.

Clustering & Session Scope

We use hardware load balancing with sticky session between our web servers and our application servers. We use the underlying J2EE Session Variables mechanism (which uses an in-memory, browser-based cookie). We rely on session scope data in many of our applications, including storing CFC instances in session scope.

If a server drops and we lose session, that user will get switched automatically to a new server in the cluster and we will have to recreate their session data. This can impact two things:

  1. Authentication level (see below for more details),
  2. Session-specific data.

If a session is lost, "Level 2" membership applications will require users to verify their login credentials again at that point ("Level 1" membership applications will be unaffected - in terms of authentication - since the "Remember Me" cookie determines that level of authentication).

Session-specific data needs a little more care:

As a general guideline, use session scope sparingly.

Use of Shared Scopes

Do not use client scope. Client scope limits you to text data (so structured data needs to be WDDX-transcoded in and out of client scope); it relies on either persistent cookies or database storage - the former is intrusive for users (and doesn't work well for shared computers), the latter introduces a potential performance hit on every request (and we try to keep database access to a minimum).

You may use session scope for user-specific data but see the caveats and considerations above.

For data caching that is not user-specific, use server scope or application scope. For macromedia.com, we're the only application in town (because we need a single session - single sign-on - across all parts of macromedia.com), so we use server scope instead of application scope (server scope access is marginally faster than application scope).

Locking & Shared Scopes

When accessing and / or updating data in shared scopes, always consider the possibility of race conditions (i.e., where two concurrent requests could access and / or update the same data). If a race condition is possible and might affect the behavior of your code, you need to use cflock to control execution of the code around the shared resource.

If the shared resource is in server or application scope, you should use named locks to control access - and choose a name that uniquely identifies the resource being locked, e.g., use server_varname when locking an update to server.varname.

If the shared resource is in session scope, you should generally use a scoped lock on session itself.

In both cases, remember that you are only trying to prevent race conditions affecting your code: most read operations do not need to be locked; most write operations should be locked (unless the race condition is either unimportant or cannot affect the outcome).

Authentication Levels & Sessions

macromedia.com has three levels of authentication:

  1. guest
  2. known, unauthenticated ("remembered")
  3. known, authenticated

Machinery exists in the root Application.cfm that establishes which state the current session is in and creates a session.membership.user object that can be queried:

For more details, your can read the session authentication spec.

« Style: Naming, Comments & Layout | Contents | Good Practice »

Comments


Telemedianer said on Feb 22, 2006 at 4:13 AM :
<cfprocessingdirective pageencoding="utf-8"/> in Application.cfm will not affect any other files than Application.cfm itself (tested with mx7sp3).

this is, btw, pretty annoying when using mx7 for older applications.
SeanCorfield said on Feb 24, 2006 at 9:01 AM :
cfprocessingdirective only affects the file it is present in. That's been true in every version of CFMX that supports the directive.

I agree that the guidelines are not clear about the recommendation: they should indicate cfprocessing directive be present in *every* file. I will update them.

 

RSS feed | Send me an e-mail when comments are added to this page | Comment Report

Current page: http://livedocs.adobe.com/wtg/public/coding_standards/structure.html