In part two we setup our web server to handle trafficing multiple domains to our single codebase, but now we have to get our applications setup. This is where we have to start thinking about separation. Since each domain is driven off the same set of physical files, we now have to consider how we separate one domain's sessions/actions/resources from another. You don't want one user's session to be cross domain in any way, and it wouldn't be good to have the media assets of one site displaying on another. Where do we begin? With the code.

Architecture Consideration: Utilizing The Server Scope

In most situations we would not utilize the Server scope, as any variables stored in the Server scope are available to every application on that server instance. In an MSOC situation, this works to our advantage, if used properly. If thought out, you can use the Server scope to store variables and objects that would be used by every application, such as shared datasource names, utility objects, error handler objects and more. By storing them in the Server scope there is only a single instance of these variables on the server instance at a time, rather than duplicating them across every application. Say you have a utility object that, when instantiated, takes up 100 KB of memory. If your server has 2,000 active applications, and that object were stored in the Application scope, that becomes 200 MB of memory. By storing it in the Server scope you use only the 100 KB, leaving more memory for active applications and sessions. Big Note: Ensuring thread safety, of objects stored in the Server scope, is absolutely vital. Before ColdFusion 9, we would want to put Server scope initialization code within our onApplicationStart() method.

view plain print about
1<cffunction name="onApplicationStart" output="false" access="public" returntype="boolean">
2    <cfif not StructKeyExists(SERVER,"dsn")>
3     <cflock scope="SERVER" timeout="5" type="exclusive">
4     <cfset SERVER.dsn = StructNew() />
5 <cfset SERVER.dsn.multihome = "multihome" />
6 <cfset SERVER.dsn.users = "users" />
7 </cflock>
8 </cfif>
9 <cfif not StructKeyExists(SERVER,"cfc")>
10 <cflock scope="SERVER" timeout="5" type="exclusive">
11     <cfset SERVER.cfc = StructNew() />
12 <cfset SERVER.cfc.GeneralUtility = createObject("component","com.multihome.GeneralUtility").init() />
13 <cfset SERVER.cfc.ErrorHandler = createObject("component","com.multihome.ErrorHandler").init(SERVER.dns.multihome) />
14 </cflock>
15 </cfif>

This adds a little overhead to application initialization and creates a lot of unnecessary code at the applicatin level, but has worked fine for previous versions of ColdFusion. Now, with ColdFusion 9, we can utilize server.cfc and the onServerStart() method for this scenario. First, you'll want to define a unique location for your multisite instance version of server.cfc. Logging in to the ColdFusion Administrator for your multisite instance, and going to the Settings page, you will find an option to define a specific location for that instance's server.cfc. In this situation, I created a config folder outside of my webroot, defined the location in the administrator, and placed the server.cfc in the folder.

ColdFusion Administrator | Settings page | Server.cfc location

After the cfc's location is defined, we can move code for Server variable declaration in to the onServerStart() method.

view plain print about
3 *     server.cfc
4 *
5 * @output false
6 */

7 component {
9 /**
10 * FUNCTION onServerStart
11 * Runs when an application is first initialized
12 *
13 * @access public
14 * @returnType boolean
15 * @output false
16 */

17    function onServerStart() {
18 SERVER.dns = {};
19 SERVER.dns.multihome = "multihome";
20 SERVER.dns.users = "users";
21 SERVER.cfc = {};
22 SERVER.cfc.GeneralUtility = createObject("component","com.multihome.GeneralUtility").init();
23 SERVER.cfc.AppConfig = createObject("component","com.multihome.AppConfig").init(SERVER.dns.multihome);
24 return true;
25    }

Once your server.cfc is in place in the folder you defined in your ColdFusion Administrator, and the server instance is restarted, the onServerStart() method is executed within the object and these Server scoped variables become available to every application defined on that instance. It is very important to note that, should you make any additions or changes to this method (for new variables, or removing unused ones) that they will not become available to your applications until the server is restarted. It is also very important to unit test any objects prior to putting them here, as it is much more difficult to track errors here. Proper error handling on object instantiation can be critical to getting your server up, so you might start thinking strategy now.

Now that we've defined some things used by all applications, let's focus a little on the applications themselves. In order to run MSOC you have to consider dynamic application naming. This is vital to separate one domain from another. Most of us, writing out our application.cfc, place code in the constructor that defines our application (this.dataSource = X). What some don't realize is that this code runs on every single page request, or that you can dynamically change those values at runtime from within the other functions of your application.cfc. Others fail to realize that you can also add additional functions to your application.cfc, that only run when called upon. This is helpful in encapsulating small bits of code to specific tasks, and not peppering your application.cfc with unnecesary variables that only need to live as long as the function.

Let's create a new method in our application.cfc, applicationSetup(), that we'll use to dynamically set our application name. We'll make our method return our application name based upon the url, and set our application name from the return of the method.

view plain print about = applicationSetup(CGI);
4 * FUNCTION applicationSetup
5 * Set app specific properties in an MSOC environment
6 * @access private
7 * @returnType string
8 * @output true
9 */

10 function applicationSetup(required cgiScope) {
11 var tmp = 0;
12 var errorMessage = "An error has occurred in initializing your application. Please contact your Support Team representative.";
13 try {
14 tmp = SERVER.cfc.AppConfig.getSiteIdByUrl(cgiScope.server_name);
15 if (tmp.success and (StructKeyExists(tmp,"queryResult") and tmp.queryResult.recordCount)) {
16 return "site_" & tmp.queryResult.siteId;
17 } else {
18 throw (type = "MH_Custom", errorCode = "001", message = "The site " & CGI.server_name & " is not setup in our system.");
19 }
20 } catch (any excpt) {
21 if (excpt.type eq "MH_Custom") {
22 errorMessage = excpt.message;
23 }
24 WriteOutput("<h1>" & errorMessage & "</h1>");
25 abort;
26 }
27 }

We passed the CGI scope into the applicationSetup() method and called the AppConfig.cfc to query the database for the siteId of the url. If an id was returned then we return the application name based upon the siteId. If none was returned, we assume it isn't a part of our system and tell that to the user before aborting further operations.

This is only the beginning, and there are many different ways of going about it. For instance, you could have the config folder as a ColdFusion mapping in the administrator, and use .ini files for configuration settings of each domain. I would use a single datasource for all domains, as you can make db additions/changes for all sites simultaneously, as you do with the code. However, some people choose to use a separate datasource per domain, for portability, faster read times, and easy per site maintenance. Everything here is a suggestion of how to procede with an MSOC environment, and opening discussion on ways to address different aspects. Have a comment? Suggestion? Let me know what you think? (Source code for this example is available through the Download link at the bottom of this post. A script for setting up the MySQL datasource used for the example is located in the config/DB_Scripts folder.)