Namespacing Components With Per-Application Mappings In ColdFusion
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 "/". 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:
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.
import
Tag in ColdFusion
A Note on the The downside of namespaces in ColdFusion is that the component dot-paths become pretty verbose. One way to get around these long paths is to import
a component directory; and then reference the components without paths. Unfortunately, the import
tag is a compile time directive, not a runtime directive. As such, we can't use per-application mappings in our import
paths.
If you define your custom mappings in the ColdFusion admin, they will work with the import
tag (or so says the documentation). However, that makes it harder to keep your custom paths in source control. Which is why I'm opting for the per-application, albeit more verbose, custom mapping solution.
Want to use code from this post? Check out the license.
Reader Comments
Post A Comment — ❤️ I'd Love To Hear From You! ❤️
Post a Comment →