Model-Driven Development with ColdFusion and UML

The sign of an experienced developer is solid design. Novices edit examples they find on the Net, journeymen figure out how to code something as they do it, but craftsmen plan their work. Starting out, this can look like wasted time, but if your app is any good, your customer will want to expand it. Then, if you need some help, you'll have to explain all that intuitive code to ten people, all of whom you are paying by the hour. Diagrams would be nice then, right?

Many development organizations are moving toward more structured development cycles, involving formal documentation standards, to ensure that code is maintainable after the developer moves away from the project. For these organizations, the challenge is to balance the demand for rapid development with the requirement for clear, solid design and analysis documents.

For developers, it is annoying to go through the trouble of writing all of those class, property, method, and argument names only to have to do it again in the database and in the code. Wouldn't it be great to do that once and then fill in the details -- kind of sketch out the application - so that the model would drive the development and get developers enthusiastic about the whole process? In this article I'll illustrate how to do this by taking a UML class diagram and using it to generate ColdFusion code. From this simple starting point, one could go on to create stubs for complex CFCs, tables, stored procedures, unit tests and even simple CRUD GUIs.

A Picture Is Worth a Thousand Words
To start, we need to find a diagramming tool that is capable of exporting its diagrams in a text format. UML tools generally seem to be using XMI (XML Metadata Interchange) to handle this, and Gentleware's Poseidon Community Edition comes to our rescue as a capable, free editor. The downside is that XMI is pretty verbose. Diagram two or three classes and you're already clocking hundreds of lines of text. The upside is that it's XML, and with ColdFusion's XML functions for parsing and searching, we'll be able to tackle this with ease... after we figure out the format of that huge file. Now that we have a tool to edit our diagrams and save them to text, we can plan our application. We're writing a tool to generate code from diagrams, so let's start by diagramming. That way we'll have something to generate at the end of this article. We know that we'll have to do at least four things: parse the XMI; create a model of our results that is easy to manipulate; generate code based on the model; and, finally, do something with the generated code (write to a file, execute, etc.). These things are likely to be complicated enough that they will involve a set of functions, so, on our diagram, we'll create four classes to start with: XMI_Parser, Generator, Model, and Writer.

Parsing the XML
First we'll have to get our tool to export an XMI version of our diagram. In Poseidon, this is as simple as File->Export Project to XMI. Open up the diagram you created earlier and export it to XMI, then open it in a text editor or an XML editor. You will see, as you browse through the file, that there is a lot of layout markup that is irrelevant to us: We're only interested in 17 of this file's 660 lines! The XML will get more and more complex as we add functions, attributes and new datatypes. We will need to find the information that is relevant to our application design, then extract and simplify it to make it easier to work with for the rest of the project. (see Figure 1) This requires a function that wraps a series of XPath queries by way of ColdFusion's xmlSearch. We'll put that on our diagram as a public function in the XMI_Parser class as "createModelFromFile," which will take a string called fileName (the path to the XMI file) and return a Model object. Poseidon shows this as "+createModelFromFile(fileName:string):Model." For our purposes, we start by identifying classes used in the diagram:

    <cfscript>
      xmlDoc = xmlparse('OpenModel.xmi');
      classes = xmlsearch(xmlDoc,"//UML:Class[@xmi.id]");
      classesLen = arrayLen(classes);
      for(i=1;i lte classesLen;i=i+1){
        thisClass = classes[i];
        //now loop down into properties, methods and arguments
      }
    </cfscript>

Deconstructing the XPath Query
Our XPath query for classes looks through the whole XML document for all elements of type UML:Class that have an xmi.id element ("[@xml.id]]"), regardless of their position in the document ("//"), and returns an array of ColdFusion XML objects that match. Listing 1 shows the XML element describing our XMI_Parser class at this point in the design.
We can see that we are interested in elements of type UML:Class that contain elements of type UML:Attribute and UML:Operation. UML:Operation elements also contain UML:Parameters, some of which are input (kind="in") and some of which are output (kind="return"). The subelements refer to other Classes or Datatypes for their type info: <UML:DataType xmi.idref = '1'/>. Somewhere in our diagram there is a Class or Datatype that has xmi.id = '1' to match this reference. These IDs are assigned by the tool to track the entities being diagrammed. When we want to determine an element's type (e.g, string, date, etc.), we can do an XPath query to look it up:

thisMethodReturnsDataType = xmlsearch(xmlDoc,"//*[@xmi.id='#thisMethod.returnParam_XMI_IDREF#']");

Creating the Model
You can see now what we have to do. We are going to loop over the results of the classes XPath query, building a ColdFusion structure as we go that simplifies and abstracts the XML we are reading. For each Class, we will loop over the nested Attributes (properties for us), then the Operations (functions for us). For each Operation, we will loop over its Parameters (arguments). At the end of the day, we have a ColdFusion structure that looks like this:

COMPONENTNAME e.g."XMI_Parser"[
   name [string]
   displayname [string]
   hint [string]

    PROPERTIES[
      PROPERTYNAME[
        name [string]
        type [string]
        required [BOOLEAN]
        displayname [string]
        hint [string]
        default [string]
      ]
    ]
    FUNCTIONS[
      FUNCTIONNAME[
        string name [string]
        access [string]
        returntype [string]
        roles [string]
        hint [string]
        output [BOOLEAN]
        ARGUMENTS[
          ARGUMENTNAME[
            name [string]
            type [string]
            required [BOOLEAN
            default [string]
            hint [string]
          ]
        ]
      ]
    ]
]

To ensure that we have a consistent interface to this data structure, we'll add functions to our Model class to add or remove information. Let's update our diagram with: addComponent, deleteComponent, addProperty, deleteProperty, addMethod, deleteMethod, addArgument, and deleteArgument. We'll also add functions called getModel (to return our populated struct), serialize (in case we want to save our struct to a file for later use), and deserialize (to populate a Model from a previously saved state), as well as a private property called componentStruct to keep track of everything as we go.


Populating the Model
Now we are ready to create a method that reads a UML diagram and populates a model of a CF application. Again, this is a simple matter of looping over all the classes we found in the XMI document and then drilling into each (Listing 2). From there, we will loop over the class properties, extracting what we find and pushing information into an instance of the Model class (Listing 3). Next, we loop over the functions and, for each of those, down into its arguments (Listing 4). When finished, our createModelFromFile function returns a Model object that represents the information in our diagram.

Generating Code from Templates
Having all of the diagram's information distilled into a struct makes it quite easy for us to generate out any kind of template code we want, adhering to whatever standard or framework fits our fancy. In the interest of time, we'll just show generation of the CFProperty section of a webservice-oriented CFC. As you'll see, it becomes simple to generate out all the code for that boring task, and generating out classes containing CFFunction 'stubs' for all of the documented functions is not much more difficult. While some code-generation frameworks encourage modeling your database first and then generating code from Entity Relationship model, I would suggest creating a class model and then generating your Data Definition Language scripts from your classes. You could go all the way and generate out stored procedures to provide supporting functions for generated stub code. I am writing such generators for my own use.

The approach used to generate code is straightforward. We create templates to generate ColdFusion or SQL code, using CFSaveContent to keep the generated content in a string buffer. To get ColdFusion to generate ColdFusion code, we use symbols to represent where in our code we want to write three special characters: <, >, and #. We want to keep the stuff we're generating fairly readable, so we substitute the double angle bracket characters for angle brackets ("") and dollar signs ($) for the pound signs. The double angle bracket character is not on your keyboard, so if you follow this method, you will have to map that character somehow. I have a macro that replaces my two typed angle brackets with one double angle bracket character. Once we have generated the whole file we want to save and have it in a string variable, we can clean up all of those placeholder symbols with the real thing with some simple string functions like replace (input,'"','>','all').

The idea is to loop over a model struct outputted from our Model.getModel() function. Below is an example of generating CFProperty code using our special templating characters. In our generation framework, this kind of code would be wrapped up in functions in the Generator class.

<cfset myModel= model.getModel() />
<cfloop collection="#myModel#" item="componentName" >
    <cfset currentCFC = myModel[componentName] />
    <cfloop collection="#currentcfc.properties#" item="propName" >
      <cfset currentProp = currentcfc.properties[propName] />
      <cfsavecontent variable="generatedProperties">
        <cfoutput>
          "cfproperty name="#propName#"
          required="#currentProp.required#"
          type="#currentProp.type#"
          hint="#currentProp.hint#"
          default="#currentProp.default#"
          displayname="#currentProp.displayname#"
        /"
        </cfoutput>
      </cfsavecontent>
    </cfloop>
</cfloop>

Saving the Code
Now that we have generated all this great stuff, we need to do something with it. Our Writer class will write out the generated content to files, but it would be nice if it would also help protect us from ourselves by, for example, moving existing files to a backup location to prevent overwrites and loss of work, and logging any problems encountered. Remember that, in many cases, the output is meant to be edited; it is just an outline for the real code. Down the road, maybe this class would be smart enough to use Subversion to try to merge changes from the model into your edited code. After cleaning up our generated code above and writing it to a file, we would see something like:

<cfproperty name="lastName"
    required="yes"
    type="string"
    hint="The User's family name"
    default=""
    displayname="Family Name"
/>

Conclusion
In this article, we have attempted to lay the foundation for a code-generation system that encourages developers to model before they code. When the model does more than fulfill documentation requirements, it becomes a more valuable tool for the developer. The blueprint for the application is generated at the start of the project, providing benefits across the development team. For organizations, model-driven development presents an opportunity to build-in standards and best practices. Senior developers and architects can focus on iteratively improving their generators to produce more code cleanly and handle mundane, repetitive tasks. Junior developers are provided a great environment for learning, and the structure they need to complete their projects rapidly. If this concept is of interest to you and your team, I encourage you to check out the OpenModel project on CFOpen and to contact me at openModel@anthrologik.net.

References

  • Author's project to develop this idea: http://cfopen.org/project/openModel
  • A great primer on XPath: http://w3schools.com/xpath/
  • The Subversion source control system: http://subversion.tigris.org/
  • Poseidon UML: http://gentlesoft.com
  • CFOpen: http://cfopen.org
  • © 2008 SYS-CON Media