Tuesday 30 July 2013

CFML: Reload Coldspring on each request when in a dev environment

G'day:
This is based on another question Rob asked about Coldspring a few days back:

Is there a way to make #Coldspring reload the CFCs on each requestion, for dev purposes? #coldfusion

I've had the same question myself, but never had to act on it so never bothered to check.

At the time I responded that I was pretty sure there was no "mode" in Coldspring to get it to change its load behaviour, and indeed one would usually only load beans when the application starts anyhow, so by the time a subsequent request comes around, everything's loaded.

Some people have code in their onRequestStart() which looks for a "reload" (or similar) URL param, and does an app-restart if it's found. This is an OK approach, but for some reason it's never sat that well with me. My feeling is that one's environment doesn't change from request to request, so it shouldn't be a request-centric process. It also leaves functionality intended for the dev environment accessible in the live environment, which isn't ideal IMO. It's not a major concern and is easily-enough worked around, but it got me thinking about alternative approaches. Not necessarily better ones, just an alternative.

Here's an approach which is based on the environment the code is running in, with the environment settings being dictated by an environment-specific config file.

In source control there's a bunch of environment config files, eg:

/config/
    environment-config-DEV.json
    environment-config-LIVE.json

Update:
As per a couple of the comments, I had been slightly too abstract for my own good in the code example, so I have modified it a bit. The intent was not to suggest one would always want to run Coldspring in a dev environment, just that if one wanted to whilst one was working on something that needed reloading every request, then it can be achieved using an environment-specific configuration. Hopefully my tweaking of the code has made this more clear. Or less unclear.

And when deploying, the deployment script takes the file appropriate for the deployment environment and deploys it as environment-config.json. In this case, a very pared down config file might be:

{
    "coldspringLoadMode"    : "REQUEST"
}


From there, the Application.cfc file reads environment-config.json (which is now environment-specific), and acts accordingly. EG:

// Application.cfc
component {

    this.name = "coldspringTest01";
    this.mappings = {
        "/coldspring" = expandPath("../coldspring"),
        "/api" = expandPath("../api")
    };

    public void function onApplicationStart(){

        application.environmentConfig = deserializeJson(fileread(expandPath("../conf/environment-config.json")));

        if (application.environmentConfig.coldspringLoadMode == "APPLICATION"){
            initColdspring();
        }

        writeOutput("Application initialised @ #timeFormat(now(), "HH:MM:SS.LLL")#<br>");
    }

    public void function onRequestStart(){
        if (application.environmentConfig.coldspringLoadMode == "REQUEST"){
            initColdspring();
        }
    }


    private void function initColdspring(){
        var properties = initColdspringProperties();

        application.beanFactory = createObject("coldspring.beans.DefaultXmlBeanFactory").init(defaultProperties=properties);
        application.beanFactory.loadBeansFromXmlFile("../conf/beans.xml", true);
        writeOutput("Coldspring initialised @ #timeFormat(now(), "HH:MM:SS.LLL")#<br>");
    }

    private struct function initColdspringProperties(){
        return {timestamp=now()};

    }

}

So Application.cfc doesn't need to have logic to decide whether it's in LIVE or DEV mode, it just loads the config file, and then uses the environment-specific settings.

From a Coldspring perspective, we then have conditional logic as to where we do its initialisation. To facilitate this we factor out the Coldspring initialisation into its own method, and then conditionally call that method onApplicationStart() (when "live"),or onRequestStart() (if "dev").

NB: the Coldspring / bean config itself is the same as in the article I knocked-out this morning.

As a test rig, I have this code:

// test.cfm
writeOutput("test.cfm initialised @ #timeFormat(now(), "HH:MM:SS.LLL")#<br>");
o1 = application.beanFactory.getBean("C");
writeOutput("o1.timestamp = #timeFormat(o1.timestamp, "HH:MM:SS.LLL")#<br>");

Which just demonstrates when o1 was initialised. On dev, the output from the first two requests look like this:

First request:
Application initialised @ 13:39:33.257
Coldspring initialised @ 13:39:33.275
test.cfm initialised @ 13:39:33.275
o1.timestamp = 13:39:33.258

Second request:
Coldspring initialised @ 13:40:31.352
test.cfm initialised @ 13:40:31.353
o1.timestamp = 13:40:31.345

So Coldspring is being init-ed  each request, and the C instance that gets assigned to o1 is - accordingly - too.

In the live environment, Coldspring is only loaded when the application starts, so o1's C instance is only created once, too:

First request:
Coldspring initialised @ 13:42:41.695
Application initialised @ 13:42:41.695
test.cfm initialised @ 13:42:41.696
o1.timestamp = 13:42:41.686

Second request:
test.cfm initialised @ 13:43:05.785
o1.timestamp = 13:42:41.686

That's it, really. I'm not saying this is a better approach to having URL-based stuff, but it's a case of different horses for different courses. Each approach has its pros and cons. And, indeed, both can be used in conjunction with each other.

I've got a meeting to go to...

--
Adam