Release 3.2 (4/14/2005)
« Style: Naming, Comments & Layout | Contents | Good Practice »
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.
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:
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.
Each file should begin with an appropriate comment - see Style: Comments.
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 StructureEven 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).
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!).
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.
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:
request.buildTag - string identifying the build on this serverrequest.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.,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.
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:
<cfapplication name="macromedia_com" sessionmanagement="true"
.../>session.membership.user etc).loc= to create request scope language / locale values - see Globalization.<!--- 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")/>
<!--- Site-wide constants --->
<cfinclude template="/environment/sitewideconstants.cfm"/>
<!--- server-specific variables --->
<cfinclude template="/environment/serverspecific.cfm"/>/{appname}/Application.cfm:
<cfinclude template="/Application.cfm"/><cfinclude template="/cfinclude/{appname}/applicationvariables.cfm"/>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>
...
This section discusses some common debugging techniques.
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.
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!
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.
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:
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:
session scope). As a general guideline, use session scope sparingly.
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).
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).
macromedia.com has three levels of authentication:
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_USERgetAuthLevelID() - numeric - 0, 1, 2 respectivelyisLoggedIn() - boolean - true if level 2 else false getUserID() - numeric - internal user ID if level 1 or 2 else
-1For more details, your can read the session authentication spec.
« Style: Naming, Comments & Layout | Contents | Good Practice »
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
Comments
Telemedianer said on Feb 22, 2006 at 4:13 AM :