CFMumps Application Design

CFMumps applications share the expressive power of the CFML language, the storage flexibility of MUMPS globals, and the ability to use external Java classes to extend their functionality beyond the abilities of CFML itself.

With CFMumps, access to MUMPS data and MUMPS code is both easy and fun, as its API is simple and straightforward.

CFML: Two Languages in One

CFML supports two complementary server-side languages; one is a tag-based templating language, superficially similar to XML. The other is CFScript, which is a very powerful JavaScript-like language. Both can be embedded within pages, and both can be used to develop robust back-end object oriented components that deliver modern JSON responses to your choice of client-side framework.

Let's take a look at a "Hello World"-style CFML fragment implemented in both languages.

Tag-Based CFML

<cfloop from="1" to="10" index="i">
  <cfoutput>I have said "Hello, world!" #i# times!</cfoutput>
</cfloop>

CFScript

for(i = 1; i < 10; i++) {
   writeOutput('I have said "Hello, world!" ' & i & ' times!');
}

Each of these two examples produce identical output, and demonstrate a tiny sampling of the flexibility and power offered by the CF environment. For the remainder of this article, I will generally give preference to the CFScript dialect, as this is the most widely-accepted best practice among modern CF experts.

Although a full tutorial in CFML/CFScript is beyond the scope of this article, Adobe provides excellent documentation. We recommend carefully working through the examples in Developing Adobe ColdFusion Applications.

Let's dive into the architecture of CFMumps!

CFMumps Architecture

CFMumps Architecture

Component Summary

lib.cfmumps.global

lib.cfmumps.global gives CFMumps the ability to interact with MUMPS global data as native CF objects, retrieving and setting entire trees and sub-trees of MUMPS globals. Its API provides the following methods:

  • open()
  • close()
  • value()
  • defined()
  • delete()
  • merge()
  • getObject()
  • setObject()

Unlike other MUMPS web application frameworks, CFMumps' globals interface fully supports MUMPS global nodes containing both data and child subscripts, and is fully compliant with David Wicksell's JSON-M specification for transporting MUMPS global data with no loss of fidelity.

lib.cfmumps.mumps

The core of CFMumps is the lib.cfmumps.mumps component, which provides an API with methods analogous to common MUMPS database operations:

  • open()
  • close()
  • get()
  • set()
  • merge()
  • kill()
  • data()
  • order()
  • query()
  • lock()
  • unlock()
  • mFunction()
  • mVersion()

These APIs will call the configured connector--either the TCP/IP-based M/Wire connector, or the high-performance, in-process GTMJI connector--to perform actions on the underlying MUMPS system. Regardless of which connector is used, the CFMumps API is identical.

With a birds-eye view of these components in mind, we can begin writing our first CFMumps application.

HelloGlobals

In the HelloGlobals application, we will look at creating an application that saves a CF object to a MUMPS global, retrieving it, and returning it as a JSON string. The example application will be a simplistic patient record.

Creating an instance of lib.cfmumps.global

First, we must create an instance of lib.cfmumps.global in order to gain access to the CFMumps global interface:

Tag-Based CFML

<cfset global = createObject("lib.cfmumps.global")>
<cfset global.open("cfmumpsTutorial", ["records", "patient"])>

CFScript

global = createObject("lib.cfmumps.global");
global.open("cfmumpsTutorial", ["records", "patient"]);

In these two lines of code, we used CF's createObject() method to create an instance of the lib.cfmumps.global component. We then opened the instance with a call to the open() method, giving it two arguments; the first representing the name of the MUMPS global on which we are operating, ^cfmumpsTutorial, and the second representing an array of subscripts within that global, ["records", "patient"]. This is analogous to the MUMPS global reference ^cfmumpsTutorial("records","patient"). Note that the open() method does not require the specified global reference to exist in the database: at this point, we have simply told CFMumps where we want this instance of lib.cfmumps.global to work.

Next, we will build up a CF object and store it in descendant global nodes of ^cfmumpsTutorial("records","patient"):

Tag-Based CFML

<cfset patient = {}>
<cfset patient["Willis, John"] = {DOB: "12/1/1980", SSN: "123-45-6789"}>
<cfset global.setObject(patient)>

CFScript

patient = {};
patient["Willis, John"] = {DOB: "12/1/1980", SSN: "123-45-6789"};
global.setObject(patient);

If we looked at a ZWRITE of ^cfmumpsTutorial after these operations, we would see the following output:

GTM> zwr ^cfmumpsTutorial
^cfmumpsTutorial("records","patient","Willis, John","DOB")="12/1/1980"
^cfmumpsTutorial("records","patient","Willis, John","SSN")="123-45-6789"

Notice that we have created a full, native CF object (patient) and saved it to the MUMPS database with a minimum of fuss.

Now, let's get John Willis' record and return it as a JSON string:

Tag-Based CFML

<cfset johnWillis = "">
<cfset patientName = "Willis, John">
<cfset global.open("cfmumpsTutorial", ["records", "patient", patientName])>

<cfset johnWillis = serializeJSON(global.getObject())>
<cfoutput>#johnWillis#</cfoutput>

<cfset global.close()>

CFScript

johnWillis = "";
patientName = "Willis, John";
global.open("cfmumpsTutorial", ["records", "patient", patientName]);

johnWillis = serializeJSON(global.getObject());
writeOutput(johnWillis);

global.close();

In this example, we have demonstrated the power of being able to project MUMPS globals as CF objects, and the built-in JSON manipulation capabilities of CFML. Next, we will flesh out this example a little more by adding more properties to our patient record, converting its functionality into object-oriented components, and adding additional properties to it.

HelloGlobals, Extended

Let's add some functionality to our demo application by converting its functionality to use CFCs (ColdFusion Components). The first file we will create is PatientRecord.cfc:

Tag-Based CFML

<cfcomponent displayname="PatientRecord" access="remote" output="false">
 
 	<cfset this.patientName = "">
 	<cfset this.record = {}>
 	<cfset this.globalName = "cfmumpsTutorial">
 
 	<cfset this.saved = false>
 	<cfset this.success = false>
 
 	<cffunction name="create" returntype="component" access="remote" output="false">
  		<cfargument name="patientName" type="string" required="true">
  		<cfargument name="record" type="struct" required="true">
  			
  		<cfset this.patientName = arguments.patientName>
  		<cfset this.record = arguments.record>
  		<cfset this.saved = false>
  
  		<cfset this.success = true>
  
  		<cfreturn this>
 	</cffunction>
 			
 	<cffunction name="open" returntype="component" access="remote" output="false">
  		<cfargument name="patientName" type="string" required="true">
  
  		<cfset var global = createObject("lib.cfmumps.global")>
  		<cfset global.open(this.globalName, ["records", "patient", arguments.patientName])>
  
  		<cfif global.defined().hasSubscripts>
   			<cfset this.success = true>
   			<cfset this.saved = true>
   			<cfset this.record = global.getObject()>
  		<cfelse>
   			<cfset this.success = false>
   			<cfset this.saved = false>
   			<cfset this.record = {}>
  		</cfif>
  
  		<cfset global.close()>
  
  		<cfreturn this>
 	</cffunction>
 
 	<cffunction name="save" returntype="component" access="remote" output="false">
  	
  		<cfset var global = createObject("lib.cfmumps.global")>
  		<cfset global.open(this.globalName, ["records", "patient", this.patientName])>
  
  		<cfset global.setObject(this.record)>
  		<cfset this.success = true>
  		<cfset this.saved = true>
  
  		<cfset global.close()>
  
  		<cfreturn this>
  
 	</cffunction>
   
   <cffunction name="getJSON" returntype="string" access="remote" output="false">
              <cfset var outputStruct[this.patientName] = this.record>
              <cfreturn serializeJSON(outputStruct)>
   </cffunction>
 
</cfcomponent>		

CFScript

component displayName="PatientRecord" access="remote" output="false" {
     
     this.patientName = "";
     this.record = {};
     this.globalName = "cfmumpsTutorial";
 
     this.saved = false;
     this.success = false;
 
     remote component function create(required string patientName, required struct record) 
     {
 	this.patientName = arguments.patientName;
 	this.record = arguments.record;
 	this.saved = false;
 
 	this.success = true;
 
 	return this;
     }
 
     remote component function open(required string patientName) 
     {
 	var global = createObject("lib.cfmumps.global");
 	global.open(this.globalName, ["records", "patient", arguments.patientName]);
 
 	if(global.defined.hasSubscripts()) {
 	    this.success = true;
 	    this.saved = true;
 	    this.record = global.getObject();
 	}
 	else {
 	    this.success = false;
 	    this.saved = false;
 	    this.record = {};
 	}
 
 	global.close();
 
 	return this;
     }
 
     remote component function save() 
     {
 	var global = createObject("lib.cfmumps.global");
 	global.open(this.globalName, ["records", "patient", this.patientName]);
 
 	global.setObject(this.record);
 	this.success = true;
 	this.saved = true;
 
 	global.close();
 
 	return this;
     }
 
     remote string function getJSON()
     {
         var outputStruct[this.patientName] = this.record;
         return serializeJSON(outputStruct);
     }
}

The above component wraps our application's data access into a single, re-usable object: a practice which will ease maintenance and provide a good programming interface on which applications can build. We have defined four methods for our PatientRecord object:

  • create(): this constructor method initializes the object with a patient name and a record structure
  • open(): initializes the object with an existing patient record, defined by the patientName argument
  • save(): writes the patient record to the MUMPS database
  • getJSON(): returns the patient record as a JSON string

Next, we will define a component called PatientList.cfc to give us a list of patients currently stored in our global:

Tag-Based CFML

 <cfcomponent displayname="PatientList" output="false">
 
 	<cffunction name="get" returntype="array" access="public" output="false">
 
 		<cfset var outputArray = []>
 
 		<cfset var db = createObject("lib.cfmumps.mumps")>
 		<cfset db.open()>
 
 		<cfset var lastResult = false>
 		<cfset var nextSubscript = "">
 
 		<cfset var subscripts = ["records", "patient", nextSubscript]>
 
 		<cfloop condition="lastResult EQ false">
 			<cfset var order = db.order("cfmumpsTutorial", subscripts)>
 			
 			<cfset lastResult = order.lastResult>
 			<cfset nextSubscript = order.value>
 
 			<cfset subscripts = ["records", "patient", nextSubscript]>
 
 			<cfif nextSubscript NEQ "">
 				<cfset arrayAppend(outputArray, nextSubscript)>
 			</cfif>
 		</cfloop>
           <cfset db.close()>
 		<cfreturn outputArray>					
 	</cffunction>
 
 </cfcomponent>

CFScript

 component displayName="PatientList" {
 	  
     public array function get()
     {
 
 	var outputArray = [];
 	
 	var db = createObject("lib.cfmumps.mumps");
 	db.open();
 
 	var lastResult = false;
 	var nextSubscript = "";
 	
 	var subscripts = ["records", "patient", nextSubscript];
 
 	while(lastResult == false) {
 	    var order = db.order("cfmumpsTutorial", subscripts);
 
 	    lastResult = order.lastResult;
 	    nextSubscript = order.value;
 	    
 	    subscripts = ["records", "patient", nextSubscript];
 
 	    if(nextSubscript != "") {
 		arrayAppend(outputArray, nextSubscript);
 	    }
 	}
 	
 	db.close();
 
 	return outputArray;
     }
 }

Notice that our get() method in PatientList.cfc is instantiating the lower-level lib.cfmumps.mumps CFMumps API, and using its order() method to retrieve a list of patient names. This is similar to the following MUMPS fragment:

  N NEXTSUB S NEXTSUB=""
  F  S NEXTSUB=$O(^cfmumpsTutorial("records","patient",NEXTSUB)) Q:NEXTSUB=""  D
  . S OUTARRAY(NEXTSUB)=""

Next, we will need to define our Application.cfc component, which provides session management for our application:

Tag-Based CFML

<cfcomponent displayName="Application" output="true">

	<cfset this.Name = "PatientRecord">
	<cfset this.ApplicationTimeout = CreateTimeSpan(0, 2, 0, 0)>
	<cfset this.SessionTimeout = CreateTimeSpan(0, 2, 0, 0)>
	<cfset this.SessionManagement = true>
	<cfset this.SetClientCookies = true>

	<cffunction name="OnApplicationStart" access="public" returntype="boolean" output="true">
		

		<cfreturn true>
	</cffunction>

	<cffunction name="OnSessionStart" access="public" returntype="boolean" output="true">
		<cfset session.auth = {}>
		<cfset session.auth.loggedIn = false>
		<cfset session.auth.loginName = "">
		<cfset session.auth.displayName = "">

		<cfreturn true>
	</cffunction>

	<cffunction name="OnRequestStart" access="public" returntype="boolean" output="true">


		<cfreturn true>
	</cffunction>

	<cffunction name="OnRequest" access="public" returntype="void" output="true">
		<cfargument name="TargetPage" type="string" required="true">
		
		<cfinclude template="#arguments.TargetPage#">

		<cfreturn>
	</cffunction>

	<cffunction name="OnRequestEnd" access="public" returntype="void" output="true">

		<cfreturn>
	</cffunction>

	<cffunction name="OnSessionEnd" access="public" returntype="void" output="false">
		<cfargument name="SessionScope" type="struct" required="true">
		<cfargument name="ApplicationScope" type="struct" required="true">

		<cfset structClear(arguments.SessionScope)>

		<cfreturn>
	</cffunction>

	<cffunction name="OnApplicationEnd" access="public" returntype="void" output="false">
		<cfargument name="ApplicationScope" type="struct" required="false" default="#structNew()#">

		<cfreturn>
	</cffunction>

	<cffunction name="OnError" access="public" returntype="void" output="true">
		<cfargument name="Exception" type="any" required="true">
		<cfargument name="EventName" type="string" required="false" default="">


		<cfreturn>
	</cffunction>

</cfcomponent>

CFScript

 component output="true" {
 
     this.Name = "PatientRecord";
     this.ApplicationTimeout = CreateTimeSpan(0, 2, 0, 0);
     this.SessionTimeout = CreateTimeSpan(0, 2, 0, 0);
     this.SessionManagement = true;
     this.SetClientCookies = true;
 
     public boolean function OnApplicationStart() 
     {
 	return true;
     }
 
     public boolean function OnSessionStart()
     {
 	session.auth = {};
 	session.auth.loggedIn = false;
 	session.auth.loginName = "";
 	session.auth.displayName = "";
 
 	return true;
     }
 
     public boolean function OnRequestStart()
     {
 	return true;
     }
 
     public void function OnRequest(required string TargetPage)
     {
 	include targetPage;
     }
 
     public void function OnRequestEnd()
     {
 
     }
 
     public void function OnSessionEnd(required struct SessionScope, required struct ApplicationScope)
     {
 	structClear(arguments.SessionScope);
     }
 
     public void function OnApplicationEnd(struct ApplicationStruct = structNew()) 
     {
 
     }
 	
     public void function OnError(required any Exception, string EventName = "")
     {
 
     }
 }

The Application.cfc component defines session variables and handlers for various application and session events. Of particular interest to our application are the variables defined in the OnSessionStart() method:

  • session.auth.loggedIn
  • session.auth.loginName
  • session.auth.displayName

These variables will later be used to handle authentication state in our application. ColdFusion, through a cookie, manages session state for us as defined in Application.cfc, making it simple for us to track session state across discrete page requests. Note that CFMumps gets this functionality for free, and because sessions persist across requests--not just AJAX requests, but also full page loads--CFMumps doesn't require you to employ the container/fragment approach for development. Other MUMPS-connected web frameworks will present a fresh login dialog each time you fully refresh the container page, but CFMumps will not.

That's it for this installment! Stay tuned for our next installment, when we will begin implementing the user interface for our application.