Introduction
Code generation is a fundamental aspect of flexible, rapid application development. There are many open source and commercial solutions available, such as Microsoft T4 or CodeSmith Tools, but often times, these tools are targeted at specific technologies or platforms. With a little legwork, you can leverage template processing libraries in modern scripting languages such as Ruby or Python to create powerful, open-ended template engines. You can use these template engines with any language or platform to generate source code, database scripts, configuration files, or any other text-based file.
Background
This particular solution uses the open source Ruby language to process templates, given a simple, flexible XML schema representing your domain model. Ruby comes preconfigured on OS X systems, is easily installed through package managers on most popular Linux distributions, and may be installed on Windows platforms using the Ruby One-Click installer found at ruby-lang.org.
Using the code
This example will use the following trivial domain schema describing a library catalog:
<Library catalog="MyCatalog">
<enumeration name="Category">
<value>FICTION</value>
<value>NON_FICTION</value>
<value>REFERENCE</value>
<value>PERIODICAL</value>
</enumeration>
<enumeration name="Format">
<value>BOOK</value>
<value>MAGAZINE</value>
<value>COMPACT_DISC</value>
<value>VHS</value>
<value>DVD</value>
</enumeration>
<object name="Media">
<property name="ID" type="string" />
<property name="Title" type="string" />
<property name="Category" type="Category" />
<property name="Format" type="Format" />
<property name="Author" type="string" />
<property name="ReleaseDate" type="DateTime" />
</object>
<object name="Author">
<property name="ID" type="string"/>
<property name="First" type="string"/>
<property name="Last" type="string"/>
</object>
</Library>
The following template file Enumerations.erb could be used to generate C# enumerations from the library domain specification:
namespace <%= catalog %>
{
<%- enumerations.each do |enumeration| -%>
public enum <%= enumeration.name %>
{
<%- enumeration.values.each do |value| -%>
<%= value %><%= ',' if value != enumeration.values.last %>
<%- end -%>
}
<%- end -%>
}
Running the command ruby generator.rb library.xml Enumerations.erb produces the following output:
namespace MyCatalog
{
public enum Category
{
FICTION,
NON_FICTION,
REFERENCE,
PERIODICAL
}
public enum Format
{
BOOK,
MAGAZINE,
COMPACT_DISC,
VHS,
DVD
}
}
The following template file Objects.erb can be used to generate simple C# domain objects from the library domain specification:
using System;
namespace <%= catalog %>
{
<%- objects.each do |object| -%>
public partial class <%= object.name %>
{
<%- object.properties.each do |property| -%>
<%= property.type %> <%= property.name %> { get; set; }
<%- end -%>
public object Clone()
{
<%= object.name %> copy = new <%= object.name %>();
<%- object.properties.each do |property| -%>
copy.<%= property.name %> = <%= property.name %>;
<%- end -%>
return copy;
}
}
<%- end -%>
}
Running the command ruby generator.rb library.xml Objects.erb produces the following output:
using System;
namespace MyCatalog
{
public partial class Media
{
string ID { get; set; }
string Title { get; set; }
Category Category { get; set; }
Format Format { get; set; }
string Author { get; set; }
DateTime ReleaseDate { get; set; }
public object Clone()
{
Media copy = new Media();
copy.ID = ID;
copy.Title = Title;
copy.Category = Category;
copy.Format = Format;
copy.Author = Author;
copy.ReleaseDate = ReleaseDate;
return copy;
}
}
public partial class Author
{
string ID { get; set; }
string First { get; set; }
string Last { get; set; }
public object Clone()
{
Author copy = new Author();
copy.ID = ID;
copy.First = First;
copy.Last = Last;
return copy;
}
}
}
If you need to add or modify domain objects, it is simply a matter of updating your XML schema. With the proper templates, you will no longer need to maintain your persistence layer or any other boilerplate code, and any bug fixes or modifications you make to your templates will be immediately available across all of your domain objects. Ultimately, you will spend less time maintaining your plumbing code, and more time doing real development.
Having your domain specification under revision control opens up some more possibilities. You will have a database backup and versioning scheme that corresponds with your repository revision (and possibly build numbers). You could reconstruct your database tables from any point in your repository. You could compare two versions of your schema and generate database rollback and change scripts. Setting up a database for a new developer will be a trivial task.
Points of interest
The Ruby script driving this template engine is very short. I have trimmed out some functionality for the sake of demonstration, but you will have access to the entire Ruby language within the engine as well as your templates, allowing you to extend the engine any way you want.
I've included another (trivial and poorly designed) domain specification to demonstrate the usage of domain specific XML tags. You can use any tag name you want, and specify any attribute you want. One requirement is that the name
attribute must be present in any complex element. XML Text
fields must also not be used for anything other than representing simple lists (as demonstrated by the following ingredient
elements).
<kingdom domain="Animal"/>
<animal name="Duck" species="Bird">
<recipe name="Bacon Wrapped Duck" difficulty="Medium">
<ingredient>10 duck breasts</ingredient>
<ingredient>3 lbs peppered bacon</ingredient>
<ingredient>2 jars banana pepper rings</ingredient>
<ingredient>Italian dressing</ingredient>
</recipe>
<recipe name="Plain Duck" difficulty="Easy"/>
<feature name="Feet" value="Webbed" color="Yellow"/>
<feature name="Tasty" value="true"/>
</animal>
...
</kingdom>
The following template snippet would allow you to iterate through your recipes:
<%- animals.each do |animal| -%>
<%- animal.recipes.each do |recipe| -%>
...
<%- end -%>
<%- end -%>
If there is enough interest, I could throw together a more practical solution demonstrating schema validation, nullable data types, multiple languages, inheritance, persistence, and build integration. Feel free to e-mail me at baker.alex@gmail.com with any questions or comments.
Helpful links