Skip to main content
Ben Nadel at Scotch On The Rock (SOTR) 2010 (London) with: Inga Lamote
Ben Nadel at Scotch On The Rock (SOTR) 2010 (London) with: Inga Lamote

Collocating Views And View-Specific Components In ColdFusion

By
Published in Comments (4)

In web application development, there's generally two philosophies when it comes to organizing files: "separation of concerns" and "collocation of behaviors". In the ColdFusion world, the pendulum or organization has swung from the collocation of behaviors—in the early days—to more of a separation of concerns within the modern MVC (Model View Controller) frameworks. But, I think the pendulum has swung too far over; and needs to return to the center where we can leverage both philosophies in the places that they make the most sense. To that end, I'll be experimenting with collocating my CFML views with the ColdFusion components that contain view-specific logic.

As I've gotten older, and I've learned a lot from my mistakes, I've come to understand that code organization is a major part of what makes an application maintainable over the long term. In that respect, one of the most important things that I can do, as an application author, is make my code easy to find and easy to delete. And, a great way to do that is to keep related files right next to each other in the file system.

I've recently started to collocate my CFML, JavaScript, and Less CSS files; and it's made some of my recent multi-page application (MPA) work much more enjoyable. The last step in that evolution is start collocating my view-specific ColdFusion components (CFCs) right next to the views that need them.

To be clear, I'm not saying that all CFCs should be right alongside the CFML views—many CFCs in a ColdFusion application are not view specific; and, shouldn't be mixed in with the CFML view files. But, many CFCs are view specific; and, shouldn't be mixed in with the "core" components.

It's helpful (to me) to think about a web application as having two macro responsibilities. There's the "application core" and the "delivery mechanism". The application core owns all of the core data models and workflows—the ColdFusion components that define the foundational meaning of the application. The delivery mechanism is the thin(ish) layer that sits on top of the application core and connects it to the outside world.

Aside: This terminology of the application core and the delivery mechanism is borrowed from Robert "Uncle Bob" Martin.

Drawing the boundary between these two responsibilities is not always easy; and it's an ongoing struggle for me. I don't pretend that this mental model is perfect—only that it provides me with a scaffolding on which to mold the application.

ColdFusion components that are view-specific and are not relating to the core application model, should not be in the "application core"—they should be in the "delivery mechanism". Where within the delivery mechanism depends on just how view-specific they are.

For example, a cross-cutting concern within the delivery mechanism, such as session cookie management, should be in a common location. But, when a component is responsible for aggregating data that is specific to a single view and is not used anywhere else within the application rendering, that ColdFusion component should be right next to the view that consumes it.

To explore this concept, let's imagine that I have a "Demo" feature. This demo feature can be a composite of several files that are collocated in the file system:

  • demo.cfm - this is the "controller" that takes the request, wrangles the data, invokes operations on the "application core", and renders the view.

  • demo.view.cfm - this is the "view" rendered by the controller.

  • demo.view.js - this is the view-specific JavaScript.

  • demo.view.less - this is the view-specific CSS.

  • demoPartial.cfc - this is the ColdFusion component that provides view-specific data aggregations and transformations.

  • demoPartialGateway.cfc - this is the ColdFusion component that executes bespoke SQL queries that pertain to the view-specific rendering needs.

Aside: I would have loved to have named the CFCs something like demo.partial.cfc, but putting dots in a ColdFusion component filename is problematic due to the way in which the dot-paths work in ColdFusion component resolution.

To see this in action, here's a sample of what the demo.cfc controller might look like. This code uses my Dependency Inject (DI) component for Inversion of Control (IoC):

<cfscript>

	// This application view has data requirements that are specific to this view and will
	// not be used by any other view in the application. As such, I think the CFCs
	// relating to the data access and aggregation should live RIGHT HERE alongside the
	// view files.
	demoPartial = request.ioc.get( "scribble.cfc-test.demoPartial" );

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	viewModel = demoPartial.getViewModel( userID = 1 );
	message = viewModel.message;
	dbData = viewModel.dbData;
	hitCount = viewModel.hitCount;

	// Render the view.
	include "./demo.view.cfm";

</cfscript>

In this case, scribble.cfc-test is the dot-delimited directory path in which both the demo.cfm and the demoPartial.cfc live. The demo.cfm file is deferring to the demoPartial.cfc ColdFusion component for the data loading. The demoPartial.cfc is also using my IoC / DI component to gain access to the gateway component:

component
	output = false
	hint = "I provide VIEW-SPECIFIC data aggregation methods."
	{

	// Define properties for dependency injection.
	property name="gateway" ioc:type="scribble.cfc-test.demoPartialGateway";

	/**
	* I initialize the view component.
	*/
	public void function init() {

		variables.hitCount = 0;

	}

	// ---
	// PUBLIC METHODS.
	// ---

	/**
	* I return the view model for the partial.
	*/
	public struct function getViewModel( required numeric userID ) {

		var dbData = gateway.getDataFromDatabase( userID );

		return {
			message: "Hello for demo view!",
			dbData: dbData,
			// Caution: this is NOT THREAD SAFE logic, I'm just using it as a demo.
			hitCount: ++hitCount
		};

	}

}

And the demoPartialGateway.cfc component is really just needed to simplify the SQL execution:

component
	output = false
	hint = "I provide VIEW-SPECIFIC data access methods."
	{

	/**
	* I get some data from the database that is only ever used for this view.
	*/
	public array function getDataFromDatabase( required numeric userID ) {

		return queryExecute(
			"
				SELECT
					( :userID ) AS id,
					( 'foo' ) AS bar,
					( 'hello ' ) AS world
				;
			",
			{
				userID: userID
			},
			{
				returnType: "array"
			}
		);

	}

}

Now that all of the view-specific files are side-by-side in the file system, making changes that affect several of the files is very easy. This code is very easy to find; and, very easy to delete should we decide that this feature no longer serves a purpose in the application.

Of course, not every view needs all of these files. Just as not every view needs view-specific JavaScript and CSS files, not every view needs a Partial component. In fact, even when I have some non-trivial data-aggregation requirements, I might just put those in the controller (see the "share controller" in my Incident Commander as an example.

As Dave Farley said, the best definition of "good code" is code that's "safe and easy to change." By collocating all of the view-specific files right next to each other, the file system itself is making assertions about the scope and relation of the files; which I believe makes the code safer and easier to change. Which in turn, makes the code "good".

Want to use code from this post? Check out the license.

Reader Comments

252 Comments

I 100% agree and try to co-locate my code whenever possible. In fact, if I have js/css that is applicable to a specific view, I just embed it in that view so that I don't even have to go to a second file. That's just my current preference, but I do a fair amount of equivocating on this.

Is the dot notation just preference demo.partial.cfc or is there another more practical reason for the naming convention? If it's just preference, maybe snake case would be worth considering demo-partial.cfc and demo-partial-gateway.cfc for overall parity?

15,908 Comments

@Chris,

The dot-notation was more about having different but similar files sort nicely in the file system. But, it causes a problem with the CFC since ColdFusion gets confused between the dots and the directory paths. To be honest, I'm not even sure if you can use a - in a CFC filename either? I'll have to try that. In which case, the - would be a nice and consistent alternative to the ..

15,908 Comments

Ok, so it looks like you can use the - in the CFC file name as long as you are instantiating it with a quoted path. Meaning, given a CFC with the filename my-test.cfc, you can either use:

createObject( "component", "my-test" );

or, by quoting the new operator target:

new "my-test"();

Given that information, the - might be a more reasonable token delimiter for grouping files. Something more like:

demo.cfc
demo-view.cfm
demo-view.less
demo-view.js
demo-partial.cfc
demo-partialGateway.cfc

Good food for thought!

252 Comments

@Ben Nadel,

Good to know! The new "my-test"() syntax though 😬. I never even would have thought to try that!
The createObject() notation isn't as cringe.

Post A Comment — I'd Love To Hear From You!

Post a Comment

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel