Select Page

Namespacing Components With Per-Application Mappings In ColdFusion

Cyberdime
Published: June 25, 2022

In my ColdFusion applications, I use a lot of components. But, these components are, for the most part, application-specific and live seamlessly alongside my application’s custom mappings. Recently, however, I’ve been thinking about writing a “module” (ie, a set of related components) that I might want to use in multiple applications. And, it wasn’t immediately obvious to me how I might define my ColdFusion per-application mappings in order to avoid conflicts with an application’s existing component tree. As such, I wanted to experiment with using per-application mappings to create “namespaces” for shared ColdFusion components.

In my mind, a “namespace” is a path-prefix that uniquely identifies a set of ColdFusion components. I’m not a Java developer; but, I believe that a common practice in the Java world is to define namespaces using this convention:

{ inverse-domain }.{ product }.{ module }

Meaning, the “Acme” corporation might use the following namespace for their analytics package:

com.acme.data.analytics

… and the “Cyberdyne Systems” corporation might use the following namespace for their analytics package:

systems.cyberdyne.data.analytics

This way, a developer could theoretically import both analytics packages without causing namespace conflicts.

If I wanted to create a product called “Strangler” (as in the Strangler pattern), I might want to use the namespace:

com.bennadel.strangler

Doing this in ColdFusion requires us to think a bit about how component paths map onto folder paths. Normally, a component path is the same as the folder path; except, with the slashes (/) replaced by dots (.). So, a ColdFusion component that resides at:

app/lib/MyComponent.cfc

… can be instantiated using the given dot-path:

new app.lib.MyComponent()

When you want to use a dot-path that doesn’t correspond to a physical folder path, you can use a per-application custom mapping to define a dot-path prefix. Historically, when I’ve done this, my “prefix” has been a single token, like lib or vendor or extensions. But, I don’t believe there is any technical reason why we can’t create a custom mapping that includes multiple, non-existent folders.

Going back to my product namespace for a second:

com.bennadel.strangler

If I were to consume this dot-path without a per-application custom mapping, I would have to have the physical folder path:

com/bennadel/strangler

But, if I only want to use this as an internal namespace and not as a file-system requirement, I can set up a per-application custom mapping that maps /com/bennadel/strangler onto an existing directory within my ColdFusion application.

To see this in action, let’s create an Application.cfc that maps the non-existent directory, /com/bennadel/kablamo, onto the current directory. Then, let’s try to use the dot-path, com.bennadel.kablamo to instantiate ColdFusion components:

component
	output = false
	hint = "I define the application settings and event handlers."
	{
	// Define the application settings.
	this.name = "CompoundPathMappingTest";
	this.applicationTimeout = createTimeSpan( 0, 1, 0, 0 );
	// When referencing ColdFusion components, paths are delimited with "." instead of
	// with "https://www.bennadel.com/". However, those paths ultimately map to folder structures. As such, if we
	// want to create a "namespace" (so to speak) for a set of components, we have to
	// create a ColdFusion mapping that lays-out the virtual file-based paths for our
	// namespace.
	// --
	// CAUTION: The mapping keys cannot end in a slash - this will break Adobe ColdFusion.
	// But, appears to work fine in Lucee CFML.
	this.mappings = {
		"/com/bennadel/kablamo": getDirectoryFromPath( getCurrentTemplatePath() )
	};
	// ---
	// PUBLIC METHODS.
	// ---
	/**
	* I handle the execution of the requested script.
	*/
	public void function onRequest() {
		// Our namespace - "com.bennadel.kablamo" - is using the "/com/bennadel/kablamo"
		// per-application custom mapping.
		var self = new com.bennadel.kablamo.lib.SubClass()
			.getSelf()
		;
		writeDump(
			label = "Compound Path Mapping Test",
			var = self
		);
	}
}

As you can see in my onRequest() event-handler, I’m using the namespace com.bennadel.kablamo to instantiate a ColdFusion component that lives in my local ./lib directory. This component is also using the same namespace to defined its structure:

/**
* NOTE: Using the namespace in "extends".
*/
component
	extends = "com.bennadel.kablamo.lib.BaseClass"
	{
	this.isSubClass = true;
	/**
	* NOTE: Using the namespace in the return type signature.
	*/
	public com.bennadel.kablamo.lib.SubClass function getSelf() {
		return( this );
	}
}

As you can see, this ColdFusion component is using the com.bennadel.kablamo namespace in both its extends attribute as well as in the return type of one of its functions.

Now, if we run this application in either Adobe ColdFusion 2021 or Lucee CFML 5.3.8.201, we get the following output:

ColdFusion component that was successfully instantiated using a compound namespace.

This output demonstrates a number of key points:

  • We were able to use per-application mappings to create the namespace com.bennadel.kablamo on top of non-existing folders.

  • We were able to use that namespace in the component’s extends attribute.

  • We were able to use that namespace in the component’s method signatures.

I don’t believe I’ve ever considered using a per-application custom mapping that includes a totally non-existent folder path! Normally, I’m just creating short aliases to existing folders. But, it’s awesome that this actually works; and, works in both Adobe ColdFusion and Lucee CFML.

Check out the license.

Source: www.bennadel.com