Skip to content

Environment Platform Specific Properties

thofrey edited this page Apr 1, 2014 · 5 revisions

Table of Contents

  1. Introduction
  2. Two Solutions
  3. Solution for Mach-II 1.6 or less
  4. Solution for Mach-II 1.8+
  5. Configuring the Environment Property
  6. Additional Information and Considerations
  7. Comments

Introduction

It is very common situation that your application requires specific properties or settings based on where your application is deployed (i.e. development, staging, production). A lot of the time, properties such as datasource names or boolean flags to turn on caching / logging are different when your application is designed to be deployed to different environments. The solution to these settings can vary from extremely simple to complex however they share a common thread. In this wiki entry, we will examine the most basic method of setting these properties.

Two Solutions

Mach-II 1.8 ships with a bundled environment property that allows you to set environment properties based on the server name where the application is deployed. Any Mach-II applications using Mach-II 1.6 or less must roll their own solutions.

Solution for Mach-II 1.6 or less

Using a Property.cfc

Mach-II 1.5 introduced a Property CFCs that allowed you to write properties that were CFCs and not just static name / value pairs as in older version of Mach-II. A Property.cfc is the perfect Mach-II hook to allow us to set properties based on the environment our application is getting deployed to. If you are unfamiliar with Property CFCs, we encourage you read up on Property CFCs.

Defining Our EnvironmentProperty.cfc

We define an Environment Property.cfc in the Mach-II XML configuration file like this:

    <mach-ii>
        <properties>
            <property name="environment" type="dot.path.to.EnvironmentProperty" />
        </properties>
        ... other XML sections ...
    </mach-ii>

As you can see, we are registering a CFC named EnvironmentProperty to a Mach-II property named environment.

Building Our EnvironmentProperty.cfc

We've already registered a property, but we have not looked at what our property does for us. Remember this is an example at the simplest and could be further expanded to support additional environments or configured via an XML file for easier configuration.

    <cfcomponent
        extends="MachII.framework.Property"
        output="false"
        hint="Provides configuration settings based on deployment environment.">

        <!---
        INITIALIZATION / CONFIGURATION
        --->
        <cffunction name="configure" access="public" returntype="void" output="false"
            hint="Sets up the configuration settings.">
            <cfset loadEnvironment() />
        </cffunction>

        <!---
        PROTECTED FUNCTIONS
        --->
        <cffunction name="loadEnvironment" returntype="void" output="false"
            hint="Loads environment specific properties in the property manager.">

            <!---
            Asking for the server name of the CGI is bad however the
            workarounds are no better
            --->
            <cfset var domain = cgi.SERVER_NAME />

            <!--- DEVELOPMENT / STAGING --->
            <cfif ListFindNoCase("myApp,myAppDev.example.com", domain)>
                <cfset setProperty("dsn", "myApp_dev") />
                <cfset setProperty("svrDevMode", true) />
                <cfset setProperty("svrCachingEnabled", false) />
                <cfset setProperty("svrLoggingEnabled", true) />

            <!--- PRODUCTION (is the default for safety) --->
            <cfelse>
                <cfset setProperty("dsn", "myApp_prod") />
                <cfset setProperty("svrDevMode", false) />
                <cfset setProperty("svrCachingEnabled", true) />
                <cfset setProperty("svrLoggingEnabled", false) />
            </cfif>
        </cffunction>

    </cfcomponent>

The CFC simply calls the loadEnvironment method when Mach-II automatically calls the configure method. This sets properties based on the server name using the server name from the cgi scope. By default, we load the production properties for safety in case the server we are deploying the application to is not in the list of development or staging environments and we do not want to expose any additional debugging information to the outside world if this situation occurs.

Solution for Mach-II 1.8+

Beginning with Mach-II 1.8, the framework ships with an environment property CFC that allows you to set the environment mode in the framework and corresponding properties. The environment property uses the server name to detect and load the correct environment properties based on where the application is loaded.

The property provides the ability to set properties for four deployment environments development, staging, qualityAssurance and production as supported by the core framework. Also, the property plays nice when used in a module by using the environment mode from the parent application when this property is defined in a module.

Configuring the Environment Property

The environment property is easy to configure by defining it in your configuration file. While the property offers the ability to configure as many environments as you wish. A lot of developers only utilize the development and production environments. If you do not need to use a particular environment, simply do not define it in your configuration file.

The property allows for an unlimited number of environments to be setup. Each environment can be named anything except for a short list of reserved names as they conflict with other parameter names used by environment property.

The names are reserved and cannot be used as names for environments:

  • defaultEnvironmentName
  • serverPropertyName

The property provides the ability to set properties for five deployment environment groups local, development, staging, qa and production as supported by the core framework for each environment. This allows modules to change their behavior based on the environment groups (i.e. conditionals by environment group name instead of explicitly checked for a concert environment name) instead of looking for a specific environment name.

Environments are resolved by server name (see serverKeyName). Since the property allows for environment resolution with server patterns, the server lists are ordered by environment groups and searched in the following order:

  • production
  • qa
  • staging
  • development
  • local

The search order driven by the environment groups (which by default "production" environments should be more "secure" than development environment groups).

Usage Warnings

Properties Are Loaded in the Order They Are Defined

Remember that Mach-II loads property CFCs in the order they are defined in the XML configuration file. Base configuration file first followed by any include configuration files in the order the includes were defined. So if propertyA needs properties set by the EnvironmentProperty and the EnvironmentProperty is defined after propertyA, the environment has not been resolved and therefore the environment specific properties have not been set. The solution is to place your EnvironmentProperty before any other properties that may need the properties defined by the EnvironmentProperty.

The EnvironmentProperty uses (via inheritance) the environment name and group from the base application if used in a module for environment resolution. This means your environment names in your module must match the environment names in your base application.

Example Configuration

    <property name="environment" type="MachII.properties.EnvironmentProperty">
        <parameters>

            <!-- Optional: Name of default environment to use if no server matches -->
            <parameter name="defaultEnvironmentName" value="production" />

            <!-- Optional: Name of property to place in name of the resolved server -->
            <parameter name="serverPropertyName" value="serverName" />

            <!-- Optional (in Mach-II 1.9.5+): Name of the key containing the server name -->
            <parameter name="serverKeyName" value="cgi.SERVER_NAME" />

            <!-- Name of environment (can be any name as 'dev-main' is an example) -->
            <parameter name="dev-main">
                <struct>
                    <!-- Name of generic environment group to assign this environment to -->
                    <key name="environmentGroup" value="development" />

                    <!-- List or array of developer servers -->
                    <key name="servers" value="dev01.example.com,dev02.example.com" />
                    - or -
                    <key name="servers">
                        <array>
                            <element value="dev01.example.com" />
                            <element value="dev02.example.com" />
                        </array>
                    </key>

                    <!-- Struct of development properties to set -->
                    <key name="properties">
                        <struct>
                            <key name="" value="" />
                        </struct>
                    </key>
                </struct>
            </parameter>

            <parameter name="production-main">
                <struct>
                    <!-- Name of generic environment group to assign this environment to -->
                    <key name="environmentGroup" value="production" />

                    <!-- List or array of developer servers -->
                    <key name="servers" value="prod01.example.com,prod02.example.com" />
                    - or -
                    <key name="servers">
                        <array>
                            <element value="prod01.example.com" />
                            <element value="prod02.example.com" />
                        </array>
                    </key>

                    <!-- Struct of development properties to set -->
                    <key name="properties">
                        <struct>
                            <key name="" value="" />
                        </struct>
                    </key>
                </struct>
            </parameter>

        </parameters>
    </property>

EnvironmentProperty Parameters

defaultEnvironmentName

The defaultEnvironmentName parameter optionally indicates which environment to load if there is no server name to another environment when loaded. For security, if no parameter is defined the environment property will throws an exception if no environment can be resolved. If the defaultEnvironmentName parameter is defined, the default environment will be loaded if no server can be resolved against the defined server names in all environments.

serverPropertyName

The serverPropertyName parameter optionally sets the name of the property used to populate the name of the server found when resolving environments. Defaults to serverName.

serverKeyName

In Mach-II 1.9.5, we added an optional serverKeyName parameter that defines the key containing the server name. By default, EnvironmentProperty will use the value stored in cgi.SERVER_NAME. However, the server name can be resolved using a Mach-II property (properties.*), an HTTP request header (headers.*), or value from the CGI scope (cgi.*). For example, if the server name is being passed in an HTTP header by a load balancer or application proxy as X-Server-Name: app01.example.com, the EnvironmentProperty can access the value using a serverKeyName of headers.X-Server-Name. Another example: a property is defined earlier in the Mach-II configuration file that performs the server name resolution and places the result into a Mach-II property named resolvedServerName. The EnvironmentProperty will use this value by setting serverKeyName to properties.resolvedServerName.

useResolvedEnvironmentNameFromParent

The useResolvedEnvironmentNameFromParent parameter optionally changes the behavior of the environment property when it is defined in a module. This parameter only applies to environment properties defined in modules and is completely ignored if set in a base application. By default the value of this parameter is true. When set to true, the environment name when resolving the environment for the module is inherited from the base application. This allows you set all the server names in the base application and leave the environment name resolution to the parent application environment property. You must have a parent environment property defined for this to work otherwise set the value to false if you want to have independent environment name resolution. When set to true, the names of the environments defined the module environment property must correspond to the same environment names defined in the parent application.

Required Keys for Each Environment

environmentGroup

The environmentGroup key takes a value from the list [local|development|staging|qa|productionServers] that is used to indicate the environment group in which the environment belongs to. This allows for modules, filters, plugins, etc. to change their behavior based on which 'generic' environment the resolved and loaded environment belongs to.

servers

The servers key takes a list or array of server names (see serverKeyName) that are designated for the named environment. This key supports basic pattern matching using the * wilcard which is useful if you deploy to a cluster (i.e. web*.cluster.example.com would match web01.cluster.example.com). For more information on how to use patterns when defining servers, check out the simple pattern matching with the SimplePatternMatcher.cfc documentation.

This key is optional in the EnvironmentProperty is defined in a module and the useResolvedEnvironmentNameFromParent` parameter is true (which it by default). This is because the environment name is gotten from the parent base application instead being resolved in the module.

properties

The properties key takes an struct of properties to be set. Each key in the properties struct can take complex datatypes like structs and arrays. These properties are set in the Mach-II property manager (<property>) and are accessed like all other properties using API methods like getProperty().

Accessing Environment Information

While the EnvironmentProperty takes care of resolving the environment and loading the properties, the core framework gives you access to environment information such as the current environment name and environment group. From any Mach-II extended component (i.e. listeners, filters, plugins and properties), you can access this information:

    getAppManager().getEnvironmentName()
    getAppManager().getEnvironmentGroup()

In Mach-II Integrity (1.9), we added helper methods available from the appManager and from inside views to check if the application is currently in an environment group or name.

Calling the appManager

    getAppManager().inEnvironmentGroup(listOrArrayOfGroupNames)
    getAppManager().inEnvironmentName(listOfArrayOfNames)

Inside views:

    inEnvironmentGroup(listOrArrayOfGroupNames)
    inEnvironmentName(listOfArrayOfNames)

Additional Information and Considerations

  • Remember that the configure method of Property CFCs are run in the order the Property CFCs are defined in the Mach-II XML configuration file. If another Property CFC depends on values set by your environment property, be sure that your environment property is defined above the other Property CFC. This is especially important if you are using ColdSpring and want to use the ${db.dsn} style parameter placeholders offered by ColdSpring. If your environment property is defined below the ColdSpring property, ColdSpring will not be able to resolve those placeholders because the properties have not been set yet by your environment property.
  • A more sophisticated environment property might set properties based off an user defined XML file for easier maintenance instead of using CFML syntax as demonstrated above.

Comments

Comment by anonymous on Thu 19 Feb 2009 05:31:35 AM EST

Can you have multiple development environments as each developer may have different paths or dsns locally?

Comment by peterfarrell on Thu 19 Feb 2009 12:24:27 PM EST

No you cannot. This really falls into the idiom that all environments mirror each other as closely as possible with only minor changes between them. This is really a best practice as everybody should have a common setup otherwise you run the huge risk of introducing scary edge case defects (which is more common than you might think). We don't want to promote this type of "dangerous" environment setup so we are opting for the more enterprise development idiom.

Comment by peterfarrell on Thu 19 Feb 2009 03:14:05 PM EST

Just wanted to clarify what I meant by "minor changes" between environments. This would be things like properties values such as all environments have a dsn property -- it is the value that differs between the environments. I'd recommend having all developers each the same dsn name for local development (depends on if you use a shared database server for local dev or each developer has a local database server on their development machine).

To answer your paths, you should strive for using relative paths that are computed from the root of your application (you can define the root with something like expandPath('.'). IMO, you're just asking for a fight if you use absolute paths.

Comment by anonymous on Wed 11 Mar 2009 11:12:10 PM UTC

Couldn't you allow generic *Servers and matching *Properties parameters so folks could have as many tiers as they want - named however they want?

At macromedia.com, we had localhost, six different shared dev environments, six matching shared QA environments, integration (pre-production) and production - because we had multiple streams of development going on at the same time.

If you allowed any fooServers / fooProperties parameters to match, you could cater to a more flexible environment setup instead of just "forcing" development / staging / QA / production on folks.

Comment by anonymous on Wed 11 Mar 2009 11:12:53 PM UTC

Weird, I gave it an email address and it still said I was anonymous.

-- Sean Corfield

Comment by mattwoodward on Thu 12 Mar 2009 10:17:49 PM UTC

Peter and I discussed this at some length this afternoon and think we came up with a more flexible alternative that will still give us some sense of order with respect to things like integration of third-party plugins, modules, etc.

The issue is if everything's literally a free-for-all then it's a constant battle of updating third-party code. The obvious example is the M2 dashboard. It probably makes sense to allow people to disable the login for local and dev boxes. But if they need to modify the environment to match theirs, then auto-updating the dashboard (which is a feature that's forthcoming--this is auto-updating the dashboard mind you, not M2 as a whole) becomes tricky because it would break on every update.

So what Peter and I came up with is modifying what we have now to support a name for a group of servers, which can be anything, as well as an environmentGroup, which will be one value from a prescribed list.

I'll send an email out to the list this evening with some details and to ask for feedback on the specific environmentGroup list people would like to see.

Seems to us that this will strike a good balance between flexibility and still having some semblance of order to things. Any additional feedback is welcome.

Also for those interested we do have a Mach-II developers list on Google--I'll send out details to the mailing list.

Comment by mattwoodward on Fri 13 Mar 2009 12:14:30 AM UTC

Following up on the above comment--here are more details we'd love to get feedback on.

What Peter and I came to was that rather than mandating parameter names such as "productionServers", etc. that parameter name can be anything. We are proposing adding a new environmentGroup key, however, that would need to be one of a prescribed list of options. The syntax would be something like this:

<!-- parameter name can be anything -->
    <parameter name="mySuperCoolProductionServers">
        <struct>
            <!-- list of servers in the mySuperCoolProductionServers group -->
            <key name="servers" value="prod1.sample.com,prod2.sample.com" />

            <!-- value of environmentGroup is one of a specific list of values; if no match 'production' is used -->
            <key name="environmentGroup" value="production" />

            <!-- property values that apply to this group -->
            <key name="properties">
                <struct>
                    <key name="dsn" value="myProductionDSN" />
                </struct>
            </key>
        </struct>
    </parameter>

The main reason for the change is if we have no control at all over anything, it becomes difficult to do things like disable the login for the Dashboard module in development environments. It would be simple enough to require people to change a setting to match one in their parent application, but then upgrading Dashboard becomes difficult since the potential for a custom value to be overwritten on the upgrade is high.

So we have the following questions for people:

  1. Does this change make sense and will it be useful? Any feedback on syntax or higher-level issues with the EnvironmentProperty? in general?

  2. Does "environmentGroup" make sense as a key name? Is "environmentType" or something else better?

  3. Regarding the list of environmentGroup/Type values, Peter and I came up with this as a starting point:

local development testing staging qa (or qualityAssurance maybe?) production

Is there other terminology people use that we're missing?

Thanks for any feedback you may have--as always the success of Mach-II depends on your input!

Comment by peterfarrell on Wed 25 Mar 2009 07:44:03 AM UTC

FYI, Matt's comment above has been entered as ticket [#254]

Clone this wiki locally