ColdFusion Components in the MX Era

Use tag-based CFCs to organize business logic while avoiding the scoping and concurrency mistakes common in older applications.

MX history: ColdFusion Components were introduced with ColdFusion MX 6. The examples here use tag-based components and MX-compatible local-variable practices.

A Simple Service Component

<!--- components/CustomerService.cfc --->
<cfcomponent displayname="CustomerService" output="false">

    <cffunction name="init" access="public" returntype="CustomerService" output="false">
        <cfargument name="dsn" type="string" required="true">

        <cfset variables.dsn = arguments.dsn>
        <cfreturn this>
    </cffunction>

    <cffunction name="findById" access="public" returntype="query" output="false">
        <cfargument name="customerId" type="numeric" required="true">

        <cfset var customerQuery = "">

        <cfquery name="customerQuery" datasource="#variables.dsn#">
            SELECT id, name, email, status
            FROM customers
            WHERE id = <cfqueryparam value="#arguments.customerId#" cfsqltype="cf_sql_integer">
        </cfquery>

        <cfreturn customerQuery>
    </cffunction>

</cfcomponent>

Creating the Component

<cfset variables.customerService = CreateObject(
    "component",
    "components.CustomerService"
)>
<cfset variables.customerService.init("support_portal")>

<cfset variables.customer = variables.customerService.findById(url.id)>

this and variables

ScopeMeaning in a CFC
thisPublic data and methods visible to callers. Avoid using it as a general dumping ground.
variablesPrivate component-instance state such as dependencies and configuration.
argumentsInputs to the current method.
var-scoped variablesTemporary state for the current method invocation in MX-era code.

Method Access

  • public: callable by normal component users.
  • private: callable only from within the component.
  • package: callable from components in the same package.
  • remote: callable through supported remote protocols; treat this as an exposed endpoint.

A Private Helper

<cffunction name="normalizeStatus" access="private" returntype="string" output="false">
    <cfargument name="status" type="string" required="true">

    <cfset var normalizedStatus = LCase(Trim(arguments.status))>

    <cfif ListFindNoCase("active,suspended,closed", normalizedStatus) EQ 0>
        <cfset normalizedStatus = "closed">
    </cfif>

    <cfreturn normalizedStatus>
</cffunction>

Remote Methods and Web Services

<cffunction name="getStatus" access="remote" returntype="string" output="false">
    <cfargument name="customerId" type="numeric" required="true">

    <!--- Authenticate and authorize before returning data. --->
    <cfreturn "active">
</cffunction>
remote is a security boundary. Validate arguments, authenticate the caller, authorize the action, restrict returned data, handle faults safely, and understand which protocols expose the method.

Inheritance

<cfcomponent extends="BaseService" output="false">
    <!--- inherited methods and new methods --->
</cfcomponent>

Inheritance exists in MX-era CFCs, but composition is often easier to understand in a maintenance project. A deep component hierarchy can make it difficult to find where a method or variable originates.

Concurrency and Long-Lived CFCs

A CFC stored in the application or session scope may serve many calls over a long period. Do not place request-specific temporary data in variables. Use properly local variables and locks only where shared state truly requires them.

Design Pattern for Legacy Cleanup

  1. Identify repeated query and business logic in templates.
  2. Move one coherent operation into a CFC method.
  3. Pass dependencies such as the datasource through an init method.
  4. Declare every temporary method variable with var.
  5. Keep presentation HTML in templates and return data from the CFC.
  6. Add characterization tests or repeatable manual checks before changing behavior further.