Skip to main content
Ben Nadel at the jQuery Conference 2010 (Boston, MA) with: Rachel Makrucki
Ben Nadel at the jQuery Conference 2010 (Boston, MA) with: Rachel Makrucki ( @m_rookie )

Moving MySQL To A Per-Application Datasource In ColdFusion 2021

By on
Tags:

Over the weekend, after my ColdFusion blog was accidentally upgraded from MySQL 5.0.10 to 8.0.28, I had to scurry and update the datasource configuration in both my production environment as well as in my local Docker development environment. And it occurred to me that manually updating the MySQL datasource in the ColdFusion Administrator represented a huge point of human failure. And, more than that, there was no record of these changes being made; which meant that undoing said changes would be quite challenging. In a modern application context, this is highly disturbing! My application's datasource configuration workflow should be in source-control. As such, I've decided to move my blog to a per-Application datasource.

SECURITY NOTE: When I say that this workflow should be in source-control, I do not mean that any credentials should be in the source-control! Never store credentials in the source-control!

Moving from a ColdFusion Administrator-based datasource configuration over to a per-application datasource configuration was easier said than done. All in all, making this change took me 3-mornings. Mostly because the documentation on this ColdFusion feature is severely lacking. That, or my Google-skills just weren't up to the task.

Ultimately, I had to piece together the MySQL per-application datasource code by picking through several other sources. First, I went to the documentation on Data Source Management for ColdFusion. This told me what kind of information I could provide in my MySQL connection; but, it didn't offer me any insight into how that information actually gets provided in the code.

From there, I went to the documentation on ColdFusion Application.cfc variables. This page started to give me some information about how the this.datasources property could be defined. But, it was still severely lacking! It didn't even have a MySQL example. And, the examples that it did have only included a fraction of the possible configuration values outlined in the aforementioned "Data Source Management for ColdFusion" article.

I thought maybe the best idea would be to look at how the datasource was configured on disk - ie, how ColdFusion stores the configuration across server restarts. Based on the documentation for, Purpose and location of XML configuration files used in ColdFusion, I learned that the datasource information was saved in an XML file, neo-datasource.xml.

So, I bashed into my local Docker CFML container and output the contents of this XML file:

NOTE: I am using CommandBox Docker Image from Ortus Solutions; and, this is where they have configured the ColdFusion server root. This may not align with your particular server setup.

# Enter the running Docker container for ColdFusion 2021.
docker-compose exec cfml bash

# Move to the configure files directory within the ColdFusion install.
cd /usr/local/lib/serverHome/WEB-INF/cfusion/lib/

# Output the entire contents of the XML file.
cat neo-datasource.xml

This gave an XML document with a bunch of <var> elements that outlined all of the datasource configuration options. From there, I embarked on a process of trial and error, plugging those values into my this.datasources code and seeing what would happen.

Half way through this process, it occurred to me that the CommandBox CFConfig module was probably messing with all of this stuff as well. After all, I've been using a .cfconfig.json file to configure my local CFML Docker container. Which means, they are possibly reading-from or writing-to this XML file.

I hopped over to cfconfig/models/BaseAdobe.cfc source code to look at their writeDatasource() method. And, oh boy, there's a lot going on here! I don't fully understand what their code is doing; but, between their code and the neo-datasource.xml file, I was finally able to get something comprehensive working!

After 3 mornings of trial-and-error and poking through various configuration files, here's what the datasource configuration in my Application.cfc now looks like:

CAUTION: While I was able to confirm that some of these configuration settings work as advertised, I wasn't sure how to go about testing the pooling configuration. So, I'm hoping there aren't any errors here; but, I cannot attest to the full accuracy of this setup.

component {

	// ... truncated for demo.

	// Define the datasources and default datasource.
	// --
	this.datasources = {
		// CAUTION: The datasource names in this struct are CASE-SENSITIVE and need to be
		// quoted. I'm not sure if this is because per-application preserve-case settings
		// do not appear to apply here; or, if something else is going on.
		"bennadel": {
			username: this.config.dsn.username,
			password: this.config.dsn.password,
			driver: "MySQL",
			class: "com.mysql.jdbc.Driver",
			url: (
				"jdbc:mysql://#this.config.dsn.server#:#this.config.dsn.port#/#this.config.dsn.database#" &
				"?allowMultiQueries=true" &
				"&characterEncoding=UTF-8" &
				"&serverTimezone=UTC" &
				"&tinyInt1isBit=false" &
				// Max Performance properties: https://github.com/mysql/mysql-connector-j/blob/release/8.0/src/main/resources/com/mysql/cj/configurations/maxPerformance.properties
				"&useConfigs=maxPerformance"
				// NOTE: Leaving zeroDateTimeBehavior as default (EXCEPTION) since I don't
				// like the idea of any data/times values being shoe-horned into a working
				// version. I'd rather see the errors and then deal with them.
			),

			// Allowed SQL commands.
			delete: true,
			insert: true,
			select: true,
			update: true,

			// Disallowed SQL commands.
			alter: false,
			create: false,
			drop: false,
			grant: false,
			revoke: false,
			storedproc: false,

			// Disables the returning of generated keys (such as PKEY AUTO_INCREMENT) in
			// the "result" meta-data structure.
			disable_autogenkeys: false,

			// These two properties seem to work in conjunction and limit the size of the
			// long-text fields that can be PULLED BACK FROM THE DATABASE. If the CLOB is
			// disabled, then the given buffer size will truncate the value coming back
			// from the database.
			// --
			// NOTE: To be clear, this DOES NOT appear to prevent the INSERT of large
			// values GOING INTO the database - just the retrieval of large values coming
			// OUT OF the database.
			disable_clob: false,
			buffer: 0, // Doesn't mean anything unless above is TRUE.

			// I ASSUME these two properties for BLOB work the same way as the CLOB
			// settings above; but, I have not tested them directly.
			disable_blob: true,
			blob_buffer: 64000, // Doesn't mean anything unless above is TRUE.

			// Connection pooling.
			// --
			// CAUTION: I have NOT VALIDATED that the following settings actually work
			// (except for the urlmap.maxConnection property).
			pooling: true,
			// The number of SECONDS before ColdFusion times out the data source
			// connection login attempt.
			login_timeout: 30,
			// The number of SECONDS that ColdFusion maintains an unused connection before
			// destroying it.
			timeout: 1200,
			// The number of SECONDS that the server waits between cycles to check for
			// expired data source connections to close.
			interval: 420,
			urlmap: {
				// Limit the number of concurrent connections to this datasource.
				// --
				// CAUTION: This value has to be a STRING - if you use a NUMBER, the
				// entire datasource configuration will fail. And, if you don't want to
				// limit connection, you have to OMIT THIS VALUE ENTIRELY.
				// --
				// maxConnections: ""
			}
		}
	};
	this.datasource = "bennadel";

	// ... truncated for demo.

}

As you can see, I'm defining a datasource, "bennadel", in this.datasources; and then, I'm using this.datasource (singular) to allocate that datasource as the default datasource within the application. This allows me to run my CFQuery tags and queryExecute() functions without any explicit datasource attribute.

Also note that the key for the datasource is quoted. This appears to be required - at least some of the time - otherwise I was getting errors that the datasource couldn't be found. That said, I can't consistently reproduce that problem. It may have been a caching issue; or, it may have been an issue with the fact that I was testing with a mixed-case datasource name (bennadelTestin originally).

After I had this per-application datasource running in my Adobe ColdFusion 2021 application, I then went into the ColdFusion Admin and deleted the CFIDE-based datasource. And, kablamo! My blog was still working!

In order to make sure that this was actually working, and not just a false positive, I had to go in and make sure that my CommandBox CFConfig was wasn't really the thing driving the datasource generation. To do this, I entered my running Docker CFML container and exported the CFConfig settings for the currently-running ColdFusion server (the one with no CFIDE-based datasource):

# Enter the running Docker container for ColdFusion 2021. This puts me into the
# site-root for my blog.
docker-compose exec cfml bash

# Enter the CommandBox CLI.
box

# Export the ColdFusion server configuration files.
cfconfig export to=./myconfig.json

This copied the myconfig.json file to my webroot, which was mounted as a volume in my Docker setup. Which means, this JSON file was accessible in my host computer. From there, I copied it over to my Docker build, destroyed my running containers, and then brought them back up again. I confirmed that the datasource was not being generated in the ColdFusion Admin; and, that my blog was continuing to run use the per-application datasource!

I was kind of surprised that the documentation on this stuff was so hard to find; especially considering how complex a full datasource configuration is. As I was saying to Brad Wood and Gavin Pickin yesterday, it must have taken the Ortus Solutions team a lot of iteration to get CFConfig working nicely across the different ColdFusion runtimes. Their code is probably the best "documentation" of how it all actually works.

All that said, I'm excited to finally have this in my source-control. This is a critical part of the application setup. And, as I demonstrated earlier this week, this kind of configuration changes over time along with your database version. As such, it needs to be recorded and persisted along with your code.

Why Not Just Commit Your CFConfig File to Source-Control?

On a recent episode of the Modernize or Die ColdFusion podcast (sorry, I don't remember which episode exactly), Brad Wood was talking about his favorite CommandBox modules. In that episode, he mentioned two things relevant to this discussion:

  • There's a module that automatically takes an .env file and applies those key=value pairs to the server so that they can be consumed as environment values in the runtime.

  • The CFConfig module can substitute some of those environment values into the .cfconfig.json file as it's being evaluated such that you can do things like generate a datasource without having to persist the datasource password to the .cfconfig.json file.

Together, this means that one could theoretically commit their .cfconfig.json file to source control as a means to manage the changes in the datasource configuration over time. So, why wouldn't I just do this rather than much with the ColdFusion per-application settings?

Simple: While I use CommandBox locally, I don't actually use it in production. My entire production setup for this blog is old school (and pre-dates CommandBox). No continuous delivery, no fancy deployment pipelines, no snazzy build processes. As such, I personally need something that works more within the boundaries of the code. Of course, if you are using CommandBox in production, I think persisting your .cfconfig.json file would be totally valid.

UPDATE: Debugging "Datasource bennadel Could Not Be Found"

CAUTION: This update did not work, see next section.

As I discussed in the comments below, after I put this per-application datasource in place, I discovered that my application would sometimes fail to bootstrap after the ColdFusion service was restated. Inside the onApplicationStart() event-handler, when I went to pre-load some data, I would end-up getting this error:

Datasource bennadel could not be found.

The crazy part about this is that if I went into the server and added code to output the application metadata at this point in the bootstrapping process:

<cfscript>

	writeDump( getApplicationMetadata().datasource );
	writeDump( getApplicationMetadata().datasources.keyList() );
	writeDump( exception.message );
	abort;

</cfscript>

... I would get the following output:

bennadel
bennadel
Datasource bennadel could not be found.

So, clearly, the datasource was defined in the application metadata, ColdFusion just wasn't seeing it.

During my trial-and-error around this problem, I discovered that - while the ColdFusion application was in this failing state - if I went into the production code and touched any of the properties in the this.datasources collection (meaning I changed one of the values), then suddenly the bootstrapping process would start working. It was like some magical, internal cache was somehow being reset when the value of the datasource changed.

This gave me the idea to add a "throw away" property to the datasource that would change whenever the application was bootstrapped. The thinking being that if the application failed to bootstrap on the first request, a second request would reload the config data and put a new timestamp in the datasource which would act as the "touch" that would clear the invalid cache state.

And, this seems to be working!

To see what I mean, here's a more fleshed-out example of my Application.cfc that shows the various parts related to the bootstrapping and configuration. Notice that the getConfigSettings() method inserts a loadedAt timestamp (tick count). This loadedAt value is then included in the datasource as the throw-away property, _loaded_at_:

component 
	output = false
	hint = "I define the application settings and event handlers."
	{

	// Define application settings.
	this.name = "WwwBenNadelCom";
	this.applicationTimeout = createTimeSpan( 2, 0, 0, 0 );
	this.sessionManagement =  false;
	this.setClientCookies = false;

	this.config = getConfigSettings();

	// Define the datasources and default datasource.
	// --
	this.datasources = {
		// CAUTION: The datasource names in this struct are CASE-SENSITIVE and need to be
		// quoted. I'm not sure if this is because per-application preserve-case settings
		// do not appear to apply here; or, if something else is going on.
		"bennadel": {
			username: this.config.dsn.username,
			password: this.config.dsn.password,
			driver: "MySQL",
			class: "com.mysql.jdbc.Driver",
			url: (
				"jdbc:mysql://#this.config.dsn.server#:#this.config.dsn.port#/#this.config.dsn.database#" &
				"?allowMultiQueries=true" &
				"&characterEncoding=UTF-8" &
				"&serverTimezone=UTC" &
				"&tinyInt1isBit=false" &
				// Max Performance properties: https://github.com/mysql/mysql-connector-j/blob/release/8.0/src/main/resources/com/mysql/cj/configurations/maxPerformance.properties
				"&useConfigs=maxPerformance"
				// NOTE: Leaving zeroDateTimeBehavior as default (EXCEPTION) since I don't
				// like the idea of any data/times values being shoe-horned into a working
				// version. I'd rather see the errors and then deal with them.
			),

			// Allowed SQL commands.
			delete: true,
			insert: true,
			select: true,
			update: true,

			// Disallowed SQL commands.
			alter: false,
			create: false,
			drop: false,
			grant: false,
			revoke: false,
			storedproc: false,

			// Disables the returning of generated keys (such as PKEY AUTO_INCREMENT) in
			// the "result" meta-data structure.
			disable_autogenkeys: false,

			// These two properties seem to work in conjunction and limit the size of the
			// long-text fields that can be PULLED BACK FROM THE DATABASE. If the CLOB is
			// disabled, then the given buffer size will truncate the value coming back
			// from the database.
			// --
			// NOTE: To be clear, this DOES NOT appear to prevent the INSERT of large
			// values GOING INTO the database - just the retrieval of large values coming
			// OUT OF the database.
			disable_clob: false,
			buffer: 0, // Doesn't mean anything unless above is TRUE.

			// I ASSUME these two properties for BLOB work the same way as the CLOB
			// settings above; but, I have not tested them directly.
			disable_blob: true,
			blob_buffer: 64000, // Doesn't mean anything unless above is TRUE.

			// Connection pooling.
			// --
			// CAUTION: I have NOT VALIDATED that the following settings actually work
			// (except for the urlmap.maxConnection property).
			pooling: true,
			// The number of SECONDS before ColdFusion times out the data source
			// connection login attempt.
			login_timeout: 30,
			// The number of SECONDS that ColdFusion maintains an unused connection before
			// destroying it.
			timeout: 1200,
			// The number of SECONDS that the server waits between cycles to check for
			// expired data source connections to close.
			interval: 420,
			// urlmap: {
			// 	// Limit the number of concurrent connections to this datasource.
			// 	// --
			// 	// CAUTION: This value has to be a STRING - if you use a NUMBER, the
			// 	// entire datasource configuration will fail. And, if you don't want to
			// 	// limit connection, you have to OMIT THIS VALUE ENTIRELY.
			// 	// --
			// 	// maxConnections: ""
			// },

			// HACK: For reasons that I don't understand at all (and what is looking very
			// much like a bug in ColdFusion), the datasources configuration occasionally
			// bombs-out when the service-start up for the first time. But, I've
			// discovered that simply "touching" this structure (ie, changing any property
			// within it) appears to fix the problem (maybe by flushing some sort of
			// internal, magical cache). As such, I'm going to see if including a
			// timestamp will act as a "touch". This timestamp denotes when the config
			// object was cached. And, since it's cached whenever the onApplicationStart()
			// method runs, if the application fails to bootstrap, the next request will
			// get a newer timestamp when it goes to reload the config.
			_loaded_at_: this.config.loadedAt
		}
	};
	this.datasource = "bennadel";

	// ---
	// LIFE-CYCLE METHODS.
	// ---

	/**
	* I get called once when the application is being bootstrapped. This method is
	* inherently single-threaded by the ColdFusion application server.
	*/
	public void function onApplicationStart() {

		var config = this.config = application.config
			= getConfigSettings( useCacheConfig = false )
		;

		// ... bootstrapping process ...

	}

	// ---
	// PRIVATE METHODS.
	// ---

	/**
	* I return the application's environment-specific config object.
	*/
	private struct function getConfigSettings( boolean useCacheConfig = true ) {

		var configName = "appConfig_#this.name#";

		if ( useCacheConfig && server.keyExists( configName ) ) {

			return( server[ configName ] );

		}

		var config = server[ configName ] = deserializeJson( fileRead( PATH_TO_CONFIG_FILE );
		// HACK: I'm having trouble with my "this.datasources" configuration bombing-out
		// on server-start for reasons that I don't understand. Through trial-and-error,
		// I've discovered that "touching" the "this.datasources" structure fixes this
		// issue. As such, I'm going to see if this date/time-stamp can act as a "touch"
		// on that value which will flush whichever cache is in an invalid state.
		config.loadedAt = getTickCount();

		return( config );

	}

}

In the onApplicationStart() method, the call to getConfigSettings() passes in the flag, false, which means that it will bypass the server scope-cache. So, every time the ColdFusion application bootstraps, it reloads and re-caches the configuration data, giving it a new loadedAt timestamp. So, if the application gets into a bad-state after the server is restarted, it will keep trying to reload the config which will then "touch" the this.datasources object by giving it a unique _loaded_at_ throw-away property.

And, like I said above, this seems to work!

CAUTION: Part of why this is working is because my onApplicationStart() method needs the datasource in order to preload some data; which is causing the application bootstrapping to fail; which means that the next request will try again to bootstrap the application. If my onApplicationStart() didn't need any data, the application would bootstrap successfully, even in a bad state. As such, the datasource would only fail later on when fetching data. This would be problematic because it wouldn't trigger a new bootstrapping process. As such, this entire HACK is predicated on the fact that the datasource needs to work during the bootstrapping process.

As far as I'm concerned, this is definitely a bug in ColdFusion. I can find no evidence, so far, that this has anything to do with how I am writing the code, especially since it only fails to bootstrap like 1 out of every 7 server restarts.

Here's the Adobe ColdFusion discussion forum where I was discussing this issue.

UPDATE: Database Or Pool Has Not Been Configured

In the previous section, I talked about including a "throw away" property in the this.datasources structure that would act as a cache-bust for whatever internal ColdFusion cache was in a bad state. And, while it appeared to work in so much as it allowed the ColdFusion application to finally bootstrap, I started to see other errors in the logs when I was programmatically manipulating the application state.

Sometimes, after I've updated a ColdFusion component, I need to re-initialize the application - without a restart - by calling the onApplicationStart() method programmatically. This instantiates new ColdFusion component instances and stores them in the application scope.

When I did this, I would see a variety of errors. Sometimes, I would see:

java.sql.SQLException: ERROR: Could not locate app_WwwBenNadelCom_bennadel. This usually means that the configuration data for this database or pool has not been configured.

Other times I would see:

java.sql.SQLException: ResultSet is from UPDATE. No Data.

And sometimes I would see:

java.lang.NullPointerException at checkColumnBounds()

When I was calling onApplicationStart() programmatically, it was applying a new tick count to the config, which was, in turn, applying a new _loaded_at_ cache-buster to the this.datasources structure. And, I believe that these errors were cause by me changing the datasource while the live application was actively receiving requests.

I decided to scrap the whole "cache busting" approach to the datasource configuration and go with something less clever and more brute force. Now, I set a special flag at the end of the onApplicationStart() that indicates that the application was successfully initialized. And, in my onError() ColdFusion application event-handler, I check for that flag. If it has not been set, then I call applicationStop() to kill the running application. And, what I'm seeing is that the next request will then - mostly likely - boot the application in a valid state.

Here's my latest Application.cfc that showcases this new approach (truncated for the demo):

component 
	output = false
	hint = "I define the application settings and event handlers."
	{

	// Define application settings.
	this.name = "WwwBenNadelCom";
	this.applicationTimeout = createTimeSpan( 2, 0, 0, 0 );
	this.sessionManagement =  false;
	this.setClientCookies = false;

	this.config = getConfigSettings();

	// Define the datasources and default datasource.
	// --
	this.datasources = {
		// CAUTION: The datasource names in this struct are CASE-SENSITIVE and need to be
		// quoted. I'm not sure if this is because per-application preserve-case settings
		// do not appear to apply here; or, if something else is going on.
		"bennadel": {
			username: this.config.dsn.username,
			password: this.config.dsn.password,
			driver: "MySQL",
			class: "com.mysql.jdbc.Driver",
			
			// .... nothing special here ....
		}
	};
	this.datasource = "bennadel";

	// ---
	// LIFE-CYCLE METHODS.
	// ---

	/**
	* I get called once when the application is being bootstrapped. This method is
	* inherently single-threaded by the ColdFusion application server.
	*/
	public void function onApplicationStart() {

		// .... truncated initialization for demo ....
		// .... truncated initialization for demo ....
		// .... truncated initialization for demo ....

		// As the very last step in the initialization process, we want to flag that the
		// application has been fully bootstrapped. This way, we can test the state of the
		// application in the onError() event handler.
		application.isBootstrapped = true;

	}


	/**
	* I handle uncaught errors within the application.
	*/
	public void function onError(
		required any exception,
		string eventName = ""
		) {

		var error = ( exception.rootCause ?: exception.cause ?: exception );

		// If the bootstrapping flag is null, it means that the application failed to
		// fully initialize. However, we can't be sure where in the process the error
		// occurred, so we want to just stop the application and let the next inbound
		// request re-trigger the application start-up.
		if ( isNull( application.isBootstrapped ) ) {

			cfheader( statusCode = 503, statusText = "Service Unavailable" );
			writeOutput( "<h1> Service Unavailable </h1>" );
			writeOutput( "<p> Please try back in a few minutes. </p>" );

			try {

				applicationStop();

			} catch ( any stopError ) {

				// Swallow error, let next request start application.

			}

			return;

		}

		// .... truncated for demo ....

	}

}

As you can see, the very last line of the onAplicationStart() method is setting the isBootstrapped to true. Then, if this flag is null in the onError() handler, it means that the ColdFusion application wasn't able to start-up successfully. In that case, I call applicationStop() to kill the current memory-space (or whatever that built-in function does).

Once the application has been stopped, the next request to the ColdFusion application boots just fine, the datasources are defined, and all the data gets pre-cached as expected. And, if I programmatically call the onApplicationStart() method to refresh some components, nothing breaks because none of the datasource properties get changed (since they are essentially static).

This is feeling like the right solution, finally! I'll be sure to report back if I run into any more issues.

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

Reader Comments

15,192 Comments

It might be worth noting that this is much easier to figure out in Lucee CFML because they have a feature wherein you can go into the server administration and then export your Application.cfc settings:

https://dev.lucee.org/t/lucee-configuration-options/321/4

So, basically, you can use the Admin GUI to get everything the way you want it, and then go about exporting a bunch of settings to see what the code-based approach should look like. That's pretty freakin brilliant if you ask me ๐Ÿ˜ฎ

13 Comments

Your comment above stole the first thing I was going to point out ๐Ÿ˜ƒ Lucee makes this MUCH simpler by giving you an export feature. You can export your entire Application settings from the Lucee admin UI or you can just edit a single datasource in the Lucee admin, scroll to the bottom, and get just the struct literal for that datasource. Super handy.

Regarding committing your .cfconfig.json, I think you covered most of it. There's nothing wrong with committing it, even if you just use CommandBox for local dev (your deployment script can remove it if you don't need it on prod). The only thing to look out for is your CFConfig will have passwords in it by default so you need to treat it just as importantly as you would the XML files in your CF home that stores the same info. That's where your .env file or just Docker secrets come into play. You can swap out any data that's sensitive, or perhaps just different per environment automatically as you export the JSON so there's no manual massaging of the file necessary.

https://cfconfig.ortusbooks.com/using-the-cli/command-overview/export-settings#json-expansion-replacements

So now you can just do your export like so

cfconfig export to=.CFConfig.json replace:datasources\.myDSN\.password=DB_PASSWORD

(this is also configurable globally as a one-time setup) and then your JSON file will automatically have the %{DB_PASSWORD} place holders swapped out for the real password, AND we'll seed your .env file to have

DB_PASSWORD=yourActualPassword

so you're ready to go and never have to worry about accidental committing anything sensitive to the repo.

2 Comments

@Ben,

In Lucee you can also see how to set any individual setting by clicking the <?/> next to the setting in the Lucee admin.

15,192 Comments

@Brad,

I didn't even think about the fact that Docker can provide secrets as well. I don't think it clicked that an ENV is an ENV is an ENV - it doesn't matter where it comes from (Docker vs. .env file). Sometimes I just miss the forest for the trees since I'm not as familiar with some of these modules.

One of these days, I really have to do a deep-dive on CommandBox.

@Andrew,

Heck yeah! They make it so freakin' sweet! This seems like something that the Adobe team could easily build in to the admin.

13 Comments

I don't think it clicked that an ENV is an ENV is an ENV - it doesn't matter where it comes from

Yep, that's the beauty of them. So when you use a placeholder like ${foo} in a JSON file, it can actually come from ANY of these places

  • An actual 'real' env variable loaded into your operating system's shell or environment in the same context that CommandBox or your server is running inside of
  • An actual 'real' env var loaded by Docker, or some secret manager on your server
  • A Java System Property in the JVM
  • A CommandBox env var set by the set foo=bar command
  • A CommandBox env var set by a the dotenv module read from a local .env file
  • A CommandBox env var set by a the dotenv module read from a global .box.env file in your user home
  • A CommandBox env var specified in the server.json file's env structure

And that's not even all the places but you get the picture-- no matter how or where you load them from, the JSON placeholders will find them and use them. That's why we use .env files on development to "spoof" the same env vars we set in production using Docker Secrets. That way our config and code stays exactly the same across all our environments and we just swap out the mechanism for loading the vars!

One of these days, I really have to do a deep-dive on CommandBox.

Ortus has put on several 1 and 2 day full in-person workshops on CommandBox. We'll be having two days of workshops before Into The Box this Sept in Houston and one of them will probably be on CommandBox. This is your perfect opportunity to come get your mind blown at ITB and take in some workshop goodness :)

15,192 Comments

@Brad,

I might have to take you up on that. Plus, I only have vague recollections of what "human contact" is like! I'm kind of itchy to get to see some people in person again.

13 Comments

I now we're a little off topic now, but I promise you if you come to ITB you won't be disappointed. Heck, we'd probably even let you give a talk on something ๐Ÿ˜‰

15,192 Comments

@All,

Over on Twitter, John Pansewisz asked where the this.config is defined. This is coming out of a config file that I am loading and then caching so that I don't have to read it off disk on every single request:

www.bennadel.com/blog/4197-performing-a-double-check-lock-around-run-once-code-in-coldfusion.htm

This was the topic of a previous post on the double-check lock pattern.

That said, it would have been nicer to be able to just pull the values out of the ENV (as Brad and I have been discussing above) and not have to worry about file I/O at all.

15,192 Comments

@All,

Ok, for some reason which I don't understand, this broke when I just restarted my ColdFusion server. I jumped into the server and quickly comment-out the urlmap sub-section, and it started working again. I am not sure why this happened, especially since I had to restart the server the first time I put the per-application datasource in place.

Just another reason that this really needs much better documentation.

15,192 Comments

For some reason, when I teardown and rebuild my Docker of Mac container locally, it all still works. I don't know what it is about the Windows environment (production) that is failing. Or maybe it has to do with how ColdFusion is persisting some settings on server-shutdown that is not happening when I just kill my Docker container? If I can figure out more, I'll post back.

15,192 Comments

Ok, so I just restarted by ColdFusion service after changing some JVM settings and I was running into the same issue - the datasource couldn't be found. I had made no changes to the actual code in that time. So, I restarted the ColdFusion service again and suddenly it started working again. I have no idea what the heck is going on here.

15,192 Comments

Ok, I think I may have found a crazy path forward. It seems that when the application got into this bad state where the datasource couldn't be found, then any "touch" of the this.datasources structure (ie, changing any of the properties) was enough to clear whatever buggy caching is happening and fix the application state.

This got me wondering if including a throw-away, dynamic property in the datasource configuration could act as this touch? To test, I started including a date/time-stamp of when the config was cache in the server scope; and then, including that in the datasource:

this.datasources = {
	"bennadel": {
		username: ....,
		password: ....,
		// .... truncated .....

		// Throw-away property that is here to do nothing else
		// but provide a property that will change when the
		// application is re-initialized.
		__loaded_at__: this.config.loadedAt
	}
};

I know this is ๐Ÿคช crazy ๐Ÿคช but, it seems to work! So, sometimes you gotta get a bit crazy. Hopefully the fix wasn't a false-positive. I'll update the post with some of this information.

15,192 Comments

I've updated the blog post with a section on debugging this issue. I'm still seeing something funky in the bootstrapping process. But, at least the app is starting up.

15,192 Comments

OK, I think I finally found a way to solve this problem in a way that doesn't cause fall-out issues as well. Ultimately, I just have to use applicationStop() when the state of the ColdFusion internal cache is corrupted. I'll post another update to the blog shortly.

15,192 Comments

I've updated the post with a new section outlining the latest applicationStop() approach to fixing the wonky state issue. I am feeling good about this one - least clever, most brute force.

15,192 Comments

I noticed that I was seeing a deprecation warning in my startup logging:

[ERROR] runwar.context: Loading class com.mysql.jdbc.Driver. This is deprecated. The new driver class is com.mysql.cj.jdbc.Driver. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.

I went into the per-application data source and changed:

class: "com.mysql.jdbc.Driver",

to:

class: "com.mysql.cj.jdbc.Driver",

... and I'm seeing that this deprecation warning is no longer showing up. Apparently this is part of the changes in the Connector/J 8 API.

7 Comments

I have a somewhat similar issue. I suspect it's related in some way.

I have a CF2021 using MariaDB. I have installed the JDBC connector. My app runs for maybe an hour and then starts throwing java.io.IOException: java.lang.reflect.InvocationTargetException. All it takes to get rid of the error is for me to go into the cfadmin and open that DSN and hit submit without changing anything. I know you are setting the DSN in your Application.cfc and I am not but I'm wondering if you are spinning your wheels trying to figure out why application.datasources isn't acting right when the real issue is buried deeper.

15,192 Comments

@Keith,

I really wish I understood more about how all the datasource stuff worked. Well, I guess, I really just wish that it worked better ๐Ÿคฃ I'd be fine not having to know about it.

At work, we ran into a lot of issues when trying to tweak timeout settings on the connections. Our data-services team wanted to go in and make all the connections timeout after 5-mins. And, no matter what we did on the ColdFusion side, we could not get our data-sources to lower how long they stays in the connection pool. So, every few minutes, we'd get a load of errors about broken connections. We tried every setting we could think of, and nothing seemed to work.

When ColdFusion's abstractions work, it's a huge productivity boost; but, when they go wonky, you gotta pay that price. I wish I had better advice.

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

Oops!
NEW: Some basic markdown formatting is now supported: bold, italic, blockquotes, lists, fenced code-blocks. Read more about markdown syntax »
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.