home Links Articles Books Past Meetings Photos SiteMap
The MDCFUG is sponsored by TeraTech. Visit us at www.TeraTech.com

Please send
comments/questions to

michael@
teratech.com

 

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

Release 3.0.2 (10/17/2003)

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

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 provides a well-designed, clean framework for building MVC-based applications. See the Mach II Development Guide for more information about object-oriented design and best practices for using that framework.

Basic Modularity

ColdFusion components, custom tags, 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 tag libraries should be used 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 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/

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. However, Application.cfm itself should be a deployable file that is 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 will be a standard, deployable source file that is 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:

  • request.buildTag - string identifying the build on this server
  • request.buildWebServer - string identifying the web server name for this back end system, e.g., "www.macromedia.com"
  • request.buildAppServer - string identifying this application server name, e.g., "d65app1.macromedia.com";
  • request.buildAppCluster - string list identifying all the application server names in this cluster, e.g.,
    "d65app1.macromedia.com,d65app2.macromedia.com,d65app3.macromedia.com"

The primary Application.cfm file will also include that, 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)
        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

There will be a root Application.cfm file that provides all the site-wide core services such as application and session settings, site-wide constants, form / URL encoding machinery etc.

Each "application" on the site will also have an Application.cfm file containing application-specific code that starts by including the root Application.cfm file. Each "application" will also typically have an include file, applicationvariables.cfm, that defines the application-specific variables. This will also be included by the application-specific Application.cfm file. 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:

  • set up application and session settings:
    <cfapplication name="macromedia_com" sessionmanagement="true" ...>
  • user session setup (sets up session.membership.user etc).
  • process loc= to create request scope language / locale values - see Globalization.
  • globalization / encoding:
    <!--- Set encoding to UTF-8. --->
    <cfprocessingdirective pageencoding="utf-8">
    <cfcontent type="text/html; charset=UTF-8">
    <cfset setEncoding("URL", "UTF-8")>
    <cfset setEncoding("Form", "UTF-8")>
  • include site-wide and server-specific constants:
    <!--- Site-wide constants --->
    <cfinclude template="/environment/sitewideconstants.cfm">

    <!--- server-specific variables --->
    <cfinclude template="/environment/serverspecific.cfm">

/{appname}/Application.cfm:

  • include root file:
    <cfinclude template="/Application.cfm">
  • include public application-specific variables:
    <cfinclude template="/cfinclude/{appname}/applicationvariables.cfm">
  • set up private application-specific data

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 exception that have that type or a more specific type, e.g.,

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

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 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

A page may accept a URL parameter debug=1 to enable debugging output within the page. Such pages should use the getrequestsettings tag which processes URL parameters into request scope. The appropriate sitewideconstants.cfm file controls whether or not debugging is enabled in a particular environment.

Debugging output within a page should be structured as follows:

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

Do not use URL.debug directly.

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:

  • If the data is being cached purely for performance reasons, then it can easily be recreated (e.g., the shopping cart in the store).
  • If the data represents historical state in a session (such as data entered in previous forms) then a judgment call should be made as to how important it is to preserve the information (e.g., by passing all the data between subsequent page requests rather than using session scope).

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:

  • getAuthLevel() - string - GUEST_USER, REMEMBERED_USER, AUTHENTICATED_USER
  • getAuthLevelID() - numeric - 0, 1, 2 respectively
  • isLoggedIn() - boolean - true if level 2 else false
  • getUserID() - numeric - internal user ID if level 1 or 2 else -1

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

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



No comments found

Source: http://livedocs.macromedia.com/wtg/public/coding_standards/structure.html


Home | Links | Articles | Past Meetings | Meeting Photos | Site Map
About MDCFUG | Join | Mailing List |Forums | Directions |Suggestions | Quotes | Newbie Tips
TOP

Copyright © 1997-2024, Maryland Cold Fusion User Group. All rights reserved.
< >