body { margin:0; padding:0; font-family:times; font-size:9pt; } p { margin-top:5; margin-bottom:5; font-weight:plain; font-family:serif; font-size:9pt; } h1 { margin-top:5; font-size:16pt; font-weight:bold; text-align:center; font-family:sans-serif; } h2 { margin-top:20; font-size:10pt; font-weight:bold; text-align:left; font-family:sans-serif; } pre { color:#993333; font-size:9pt; } code { color:#993333; } .section { text-align:justify; padding:5; }
The "Northwind" database is used by Microsoft as a teaching example. The aim of this paper is to use this data model as the basis for a demonstration of the Cut The Crap "Alchemist".
The Alchemist is a system generator. From either a definition file or using
its own API, the Alchemist will generate a complete object model implementation.
The generated system includes a jar file with the compiled code,
javadocs for the generated API, and a war file for instant
web deployment in a tomcat server. Since the generated object model
uses the Generic Persistent Object model, all Alchemist generated systems are scalably
persistent to gigabytes of data and deployable in muti-user contexts.
The Alchemist works by identifying common patterns. These are not simply low-level programming patterns, but ones more akin to overall application structure. How is a system initialized? - or - How is data created?
This paper attempts to de-mystify the patterns used and demonstrate the correspondence between the provided user definitions and the code generated.
This is a meta-representation of the target model. In other words it describes the properties and object associations of the model.
Why define a new modelling language? Because it is essential to have the freedom to add and remove language features as required. The aim of this language is to support the system generation, nothing more or less.
For the purposes of this paper the Alchemist API will not be addressed, instead we will provide
the definition using an ORM file. This is an xml file that will have the following
structure:
<orm name='Northwind' package='alchemy.northwind'> ...definitions </orm>
There are just two types of definitions, class definitions and
assoc definitions.
A frequent oversight in most object modelling, is that there is rarely a representation of the Application or System itself.
Suppose, for example, that you wanted to model some business - let's call it Northwind.
The modelling process tends to start by considering all the entities within Northwind.
We may have, Customers, Suppliers, Products and
Orders to start off with.
We would then come up with some object or entity diagram to show how these things related to each other.
But what about including an object to represent Northwind itself? Surprisingly
enough, by introducing such an object as the focus of the overall model, things start to look
far more sensible.
Rather than saying "there are Customers", we state that "Northwind has many
Customers", etc...
When someone asks "How do I start interacting with this system?", the root object
- in this case Northwind defines the initial interactions.
For this reason, a definition for The Alchemist requires a root object, here is the
definition:
<class name='Business' isroot='true'>
<attr label='Name'
type='java.lang.String'
islabel='true'
default='Northwind'/>
</class>
We have defined a class called Business, declared that it is the root
class and provided a single Name attribute, that should be used to label any instance
and is given the default value "Northwind".
Oooops... we have already stumbled over a number of patterns.
In order to display an object to the user in some understandable form it is normal to use
some property of the object. The islabel definition attribute indicates this.
...and while we are at it, it is often useful to declare default values for some attributes.
This is already sufficient for a system to be generated. A client object called
NorthwindClient will be generated to support application initialization. This can be
created by:
client = new alchemy.northwind.client.NorthwindClient();
Other constructors provide options to store the model using different GPO
object managers.
When the client is created, it checks to see if the root object already exists, if not then it creates one.
The client would have a method getBusiness() that would return a reference
to the root object.
The root object is an instance of alchemy.northwind.Business and
provides methods to both getName and setName, when the Business
object was first created it will have set the Name property to the default "Northwind"
value as specified in the ORM.
A generated war file could be deployed in a Tomcat server, access to the northwind
application would explain that it had been generated by the Alchemist, invite you to inspect the
javadocs or to open an inspector that by default would focus on the
root Business object. With this inspector you could view and modify the single
specified property.
As you start to edit the property a "Submit" button is made visible. At any time you could "Revert" to the state currently stored, or you could "Submit" the update. When submitted you will then see two more buttons "Revert" and "Save", these provide the transaction control. Any number of submits may be made within the same transaction.
Let us continue.....
Take a look at a more complete Business class definition:
<class name='Business' isroot='true'>
<attr label='Name'
type='java.lang.String'
islabel='true'
default='Northwind'/>
<attr label='Address'
type='java.lang.String'
guihint='rows:3'/>
<attr label='Postcode'
type='java.lang.String'
restriction="postcode"/>
<attr label='TelNo'
type='java.lang.String'
restriction="telno"/>
<attr label='EMail'
type='java.lang.String'
restriction="email"/>
</class>
You will notice that as well as defining several more properties, that two more definition attributes have been introduced.
The guihint attribute is just that, a hint to how the field should be displayed.
In this case guihint='rows:3' indicating that any GUI should provide a multi-row
editing box of three rows.
More interesting is the restriction attribute. The Alchemist uses
an external file that provides aliases for restrictions. Here is
a snippet of the file:
<entry alias="postcode"
value="regex:^[A-Z]{1,2}[0-9]{1,2}( [0-9][A-Z]{2})?$"
example="RG41 1JT"
/>
Yep, that's right, there are a number of different kinds of restrictions, and one of the
especially useful ones is a regular expression.
The restrictions.xml file declares several useful aliases and you are able to
add new definitions or modify existing definitions as you see fit.
Generally, a restriction is a type restriction, other restrictions might
be range:1-10 for a numerical value, or timeslot:hours for a Date
type.
The restrictions as defined in The Alchemist model definition file
are used to generate meta data that can be exploited by validating user interfaces. Here is
a snippet from the generated getMetaSpec method that is used by several generic GUI
tools:
public MetaSpec getMetaSpec() {
MetaSpec spec = new MetaSpec(this);
Object lblval = get(m_name);
if (lblval != null) {
spec.setLabel(lblval.toString());
}
// attributes
spec.addAttribute(m_name, "Name",
get(m_name), String.class, null, null);
spec.addAttribute(m_address, "Address",
get(m_address), String.class, "rows:3", null);
spec.addAttribute(m_postcode, "Postcode",
get(m_postcode), String.class, null,
"regex:^[A-Z]{1,2}[0-9]{1,2}( [0-9][A-Z]{2})?$");
...
A key point here is that the meta data that has been used to generate the model is itself available at runtime to tools that know how to process it.
Before we can look at associations we need to define another class. The "Northwind" system requires
Customers and Suppliers, however, we'll assume that we have a few minutes
time to think about it and recognise that these can both be thought of as Contacts.
<class name='Contact'>
<attr label='Name'
type='java.lang.String'
required='true'
islabel='true'/>
<attr label='TelNo'
type='java.lang.String'
restriction="telno"/>
<attr label='Email'
type='java.lang.String'
restriction="email"/>
<attr label='Address'
type='java.lang.String'
guihint='rows:3'/>
<attr label='PostCode'
type='java.lang.String'
restriction="postcode"/>
<attr label='Notes'
type='java.lang.String'
guihint='rows:4'/>
</class>
Now we are ready to define an association:
<assoc> <one src='Business' owner='true'/> <many src='Contact' classified='Name:alpha'/> </assoc>
Okay, we are saying here that there is a one-to-many association between
a Business instance and instances of Contact.
The owner attribute indicates that the Business instance "owns" the
Contact instances. From this The Alchemist will generate methods for the
Business class to create new instances of the Contact class.
Note though that the Name property has been indicated as required, this
means that it must be provided when the object is created, so what method do we see generated
for the Business class?
/*************************************************************
* Create a new contact.
* @return the new contact.
**/
public IContact createContact(String name) {
cat.debug("Creating new contact.");
startNativeTransaction();
Contact ret = new Contact(getObjectManager(), name);
addContact(ret);
commitNativeTransaction();
return ret;
}
We can see from the code that the generated Contact constructor requires
a name argument, and the generated createContact method will
provide it.
This is precisely what we should expect, we can just ask the root object:
contact = northwind.createContact("Customer One");
This has created the new object, persistently stored it in the datastore and returned a reference to it.
Furthermore, we have stated that the many Contacts are classified with
the value Name:alpha. This indicates that the set of Contacts defined by
the one-to-many association is classified by the value of the Name
attribute of each Contact. The alpha parameter can be used by user
interface tools as an indication of how such a classification should be navigated.
The Alchemist will generate lookup methods on the Business class
that access the classification object to find Contacts according to the value of
their Name attribute.
Okay we'll add in some more classes and associations we need, hopefully you should already be able to understand most of what is declared:
<class name='Product'>
<attr label='Name'
type='java.lang.String'
required='true'
islabel='true'/>
<attr label='Price'
type='java.lang.Double'/>
<attr label='Description'
type='java.lang.String'
guihint='rows:3'/>
</class>
<assoc>
<one src='Business' owner='true'/>
<many src='Product' classified='Name:alpha'/>
</assoc>
<assoc>
<one src='Contact' knownas='Supplier'/>
<many src='Product' classified='Name:alpha'/>
</assoc>
<class name='OrderItem'>
<attr label='Label'
type='java.lang.String'
islabel='true'
formula="$Product!$Name"/>
<attr label='UnitPrice'
type='java.lang.Double'
formula="$Product!$Price"/>
<attr label='Quantity'
type='java.lang.Integer'
default="1"/>
<attr label='Price'
type='java.lang.Double'
formula="(* $Quantity $UnitPrice)"/>
</class>
<!-- An OrderItem make no sense without a Product -->
<assoc>
<one src='Product' required='true'/>
<many src='OrderItem'/>
</assoc>
Whooa! What's all this formula stuff?
The Alchemist now provides support for spreadsheet-type formulas within the
object model. These generate GPO formulas - check out the separate whitepaper
on GPO Formulas.
The first formula $Product!$Name defines the value of the OrderItem
label. The formula indicates that the value of the label is computed by accessing the "Name"
property of the associated "Product". If the formula had been $Product$Name then the
value of the order item would be updated should ever the product name be changed, while the formula
used includes the '!' character, this ensures that the dependency stops at the "Product" link and that
the Label of the OrderItem is the same value as when it is created.
The '!' character is also used to define the UnitPrice of the OrderItem,
calculating the value at the time it is created.
Lastly the Price is calculated by multiplying the value of the UnitPrice
by the Quantity.
The syntax for defining formulas within the ORM file is slightly different from when defining
directly in GPO. This is because of the mapping required between the names used in
the ORM and the generated names in GPO. Specifically this involves the lookup
formula. So $Product for example will generate (-> orderitem/product) and
here is a snippet of the generated code for the OrderItem class:
public OrderItem(IGPO _context, IProduct product) {
super(_context);
setProduct(product);
// Set any default attribute values.
setQuantity(new Integer("1"));
// Define any formulas.
define(m_label, "(-> orderitem/product ! product/name)");
define(m_unitPrice, "(-> orderitem/product ! product/price)");
define(m_price, "(* (-> orderitem/quantity) (-> orderitem/unitprice))");
registerDependencies();
cat.debug("Constructed.");
}
Next up is the Order class:
<class name='Order'> <attr label='Date' type='java.util.Date' isLabel='true' restriction='timeslot:day'/> <attr label="Price" type='java.lang.Double' guihint='numberformat:###,##0.00' formula="(sum $$OrderItem.$Price)"/> </class>
This definition has a few new features:
There is a Date attribute that
has the declared rstriction timeslot:day, indicating that only the date and
not the precise time is of interest.
The Price property defines a numberformat guihint that
is used to display the value returned by the formula (sum $$OrderItem.$Price),
this is a "set-based" computation and the $$OrderItem indicates the relevant
association.
The effect of this formula is to incrementally compute the Price of the
Order as OrderItems are added, removed or modified.
<assoc> <one src='Business' owner='true'/> <many src='Order'/> </assoc> <assoc> <one src='Order' owner='true'/> <many src='OrderItem'/> </assoc> <assoc> <one src='Contact' knownas='Customer' required='true'/> <many src='Order'/> </assoc> <assoc> <one src='Contact' knownas='DeliveredTo'/> <many src='Order' knownas="ReceivedOrder"/> </assoc>
No - it is substantially more than the normal Northwind model.
We have generalized the Northwind Customer and Supplier classes
and recognized that an Order is for a Customer Contact, includes
Products provided by Supplier Contacts and DeliveredTo a
possibly different Contact.
So our Northwind model handles nicely the role of a general distributor where Contacts
can be Suppliers, Customers and Recipients.
Here is a snippet of the generated IContact interface, stripped of javadocs
for readability:
public IProductIterator getProducts(); public void addProduct(IProduct product); public void removeProduct(IProduct product); public IProduct getProductByName(String name); public IOrderIterator getOrders(); public void addOrder(IOrder order); public void removeOrder(IOrder order); public IOrderIterator getReceivedOrders(); public void addReceivedOrder(IOrder receivedOrder); public void removeReceivedOrder(IOrder receivedOrder);
Note the use of the typed iterators, where for example an IProductIterator
provides a nextProduct() method to help in clearer programming. These
iterators derive from the Cut The Crap Striterator class that provides
useful methods to process and filter such object streams, for more information check out the
whitepaper.
Furthermore, we have included formulas that dynamically calculate Order prices and initialize
OrderItems.
We also have an API that correctly addresses object creation, so the Business
instance will create new Orders - with a required Customer contact:
public IOrder createOrder(IContact customer);
An Order instance will create OrderItem instances with a required
Product:
public IOrderItem createOrderItem(IProduct product);
...and so on
In short, we have generated a complete system, not simply some database that allows the data to be stored.
Now you have got this far, take a look at the complete ORM definition file.
With this model definition The Alchemist will generate a complete
persistent object model - building on the underlying GPO system.
The ant build files will build a deployable zip file
including javadocs and jar files.
A recent enhancement, is the generation of a Struts and Tiles based JSP web
deployment - war file. When deployed, the Web Application provides a highly functional
interface to the generated system. The guihints and restrictions
are utilized to maximum effect to provide browser-based validation of user entered text
"as they type", and drag-n-drop object associations alongside effective interfaces
to navigate the set and lookup structures.
The Web Application also includes user management and login security using standard security techniques.