Showing posts with label Coldspring. Show all posts
Showing posts with label Coldspring. Show all posts

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

Installing Coldspring 1.2

G'day:
This article is partly a piss-take, and partly actual guidance. Mostly the latter. Thanks to Rob Glover (again!) for the inspiration for the article (both the piss-take and the actual meat of the article).

Whilst on the train this morning, I read this Twitter update from Rob:

Amazing. NOWHERE can I find INSTALLATION INSTRUCTIONS for #coldspring. Just lots on how it works. #coldfusion
That is quite amazing, but not in the way Rob means. Because a quick google of "coldspring install" returns a bunch of results, and just browsing through the first few:

  1. ColdSpring - Home
    Well... yeah, OK: this is a fail. I've done superficial clicking around, and didn't see any installation instructions within the first ten clicks of hitting the home page.
  2. [PDF] Reference Guide - ColdSpring
    The third item is the installation instructions.
  3. How to install the ColdSpring framework - Hosting Forum - Hostek ...
    The raison d'ĂȘtre of the document is that its instructions of how to install Coldspring.
  4. Mach II and Coldspring for n00bs | Ollie Jones, software hints and tips
    Includes instructions for installing Coldspring
  5. ColdSpring - Railo
    Is not actually about installing Coldspring.
  6. A Beginner's Guide to the ColdSpring Framework for ColdFusion ...
    Unfortunately - given the title - doesn't actually include how to install it.
From here, the docs are referring to Coldspring installation / config issues, or tangential issues people have had, so not really relevant. Still: in the first six Google results, five are about Coldspring installation, and three of them have instructions.

So what's amazing about this is that Rob couldn't find any of this stuff.



But just for the sake of completeness, here are some instructions for installing Coldspring.

Update:
As Mark Mandel has pointed out to me, I only cover 1.2 here, and there is also 2.0 in the works (the status of it is TBC... I don't actually know). I'll find out if 2.0 works differently, and write a companion article if required.
  1. Download Coldspring from the Coldspring website. At time of writing, the current stable version proclaims to be 1.2, and there's an alpha of 2.0 available too. There's a download link for 1.2 (zip) on the homepage.
  2. Unzip the file into a non web browsable directory. Do not put it in your web root (this conflicts with most of the instructions you will see). After unzipping, you should have this sort of directory structure:

    /[parent directory]/
        /[coldspring directory]/ (can be called anything, but "coldspring" makes sense)
            /beans/
                    DefaultXmlBeanFactory.cfc
    
    There will be a lot of other files in there, but that gives you an idea of what the directory structure is like.
  3. In you application's Application.cfc, create a ColdFusion mapping to the [coldspring directory], eg:
    this.mappings = {
        "/coldspring" = expandPath("../coldspring")
    };
    In this case, my web root is adjacent to the coldspring dir, eg:
    /[parent directory]/
        /coldspring/
        /webroot/
            Application.cfc
    
  4. as an alternative to creating the mapping in Application.cfc, one could instead create it in CFAdmin, but I think doing it in the application is preferable.
That is it as far as installation goes.

Note that a lot Coldspring installation instructions will by default suggest just dumping the Coldspring directory in the web root, because this is "easier" because it doesn't require the mapping. This is really poor advice for a few reasons:
  • It's not exactly difficult to create the mapping.
  • The Coldspring files are not intended to be web browsable, so why would the instructions suggest putting them in the web root? That's just wrong.
  • Having files not intended to be web browsed in a web-browsable directory is a potential vector for site hacking. 
In fact most framework installation instructions prefer the lazy / wrong / insecure installation approach to the equally-easy / good / secure approach. So when you read framework installation instructions which advocate putting the framework in the web root as a default practice, screw your nose up in disgust, and don't do that. Generally a framework will be happy to be installed anywhere, and all it needs is a CF mapping with a known name (eg: /coldspring in this case).



So the install for Coldspring is easy. Here's a sample bare-bones application building on that, showing some of the config.

// Application.cfc
component {

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

    public void function onApplicationStart(){
        var timestamp = now();

        writeOutput("application initialised @ #timeFormat(timestamp, "HH:MM:SS.LLL")#<br>");

        application.beanFactory = createObject("coldspring.beans.DefaultXmlBeanFactory").init(defaultProperties = {timestamp=timestamp});
        application.beanFactory.loadBeansFromXmlFile("../conf/beans.xml", true);
    }

}

Here we have our /coldpsring mapping, and the bootstrap code to get Coldspring loaded, and my beans loaded too.


I'm passing a bean config parameter too.

Here's my bean definition XML file:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <bean id="C" class="api.C">
        <constructor-arg name="timestamp">
            <value>${TIMESTAMP}</value>
        </constructor-arg>
    </bean>
</beans>

Note that the /config directory is also not in the web root. It's adjacent to the /coldspring and /webroot and (see below) /api directories.

And I obviously have this C.cfc file too:

// C.cfc
component {

    public C function init(required date timestamp){
        this.timestamp = arguments.timestamp;

        writeOutput("C initialised @ #timeFormat(timestamp, "HH:MM:SS.LLL")#<br>");

        return this;
    }

}

Finally, I have a file which uses Coldspring to fetch my bean:

// 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>");
sleep(5000);

writeOutput("current time #timeFormat(now(), "HH:MM:SS.LLL")#<br>");
o2 = application.beanFactory.getBean("C");
writeOutput("o2.timestamp = #timeFormat(o2.timestamp, "HH:MM:SS.LLL")#<br>");

And the output from this is as follows:

application initialised @ 08:38:21.029
test.cfm initialised @ 08:38:21.053
o1.timestamp = 08:38:21.029
current time 08:38:26.066
o2.timestamp = 08:38:21.029

What the timestamps show is that the C bean is created once, during application start-up, and then calls to getBean() simply return that bean. Note that I pause for 5sec in test.cfm, but the timestamp o2 has is the original timestamp from app start-up, not the current timestamp from when o2 was created.


There's obviously a bunch more config options for Coldspring, but this is not an attempt to replicate the docs, it's just a top level summary of how to install it, and a bare bones example app showing the minimum config.

Hopefully this will help people trying to find Coldspring installation docs online.

I've got another article to come on Coldspring: also based on a question Rob asked the other day. Stay-tuned.

--
Adam