Model Driven Architecture and Code Generation

Model Driven Architecture and Code Generation

MDA Overview

MDA Overview

I have been using Model Driven Architecture and code generation for some time. In this short blog post I have tried to summarise some of the lessons I have found useful. I have included an example using Sparx EA.  Happy to share the code but I haven't yet been bothered to package it up!

1. Code Generation - Use Appropriately

Code generation is old hat of course ... for a long time designers have wanted (and often succeeded) to transform their detailed architectural models into physical implementations. The aim of model driven code generation is increasing productivity and to better document or communicate the details of the system in question.

The danger is you end up really just creating ... well ... 1970's flow diagrams, large pictures of program logic. Time consuming to produce and giving no additional insight; much easier to code in a good productive modern language.

What I am saying is that generating Java EJB (for example) code from a hand crafted UML model of all the required J2EE classes is just pointless. What you need to do is transform a simpler logical "technology independent" model into implementation "physical" models.

2. Conceptual is not the same as Technology Neutral

Another mistake is to try to automatically transform conceptual models into models that can be the target for code generation. Conceptual is not the same as technology neutral, a conceptual model is designed to help develop and communicate how a system works. To aid understanding unnecessary and confusing details will have been missed out, and relationships between components will be "idealised" ... lies designed to elucidate if you like. On the other hand a technology neutral model needs to be exact if it is going to be the root source for code generation.

Conceptual models have a purpose, don't ruin them ...

3. Keep Generated Code Simple and Readable

Generated code is hard to debug and fix. Changing code involved changing your code generator; the code generator will inevitably become complex and ugly. Tracking causes between the generated code, input model and the generator logic will be frustrating ... especially if it has been sometime since you have looked in detail at the generator logic.

Some users of your generated code may not have access to the generator (or more to the point may NOT want access to it). Of course changing the code manually is a mistake, but nethertheless they should be able to understand the code. In a way it should look like manually generated code.

Generated code should be made to be simple and clean ... use object oriented encapsulation (not quite the right word but you know what I mean) or libraries to separate the handcrafted, involved and complicated plumbing from the automatically generated simple if repetitive and large code base. 

Example - Google App Engine Datastore

My UML tool of choice is Sparx EA, and this has good MDA capabilities:

  • A two stage generating methodology. First to transform from a technology neutral model to technology specific model, secondly to generate code (Java, C#, SQL DDL etc) from the corresponding technology specific model. 
  • A common templating engine for transformations and code generation.
  • A simple Domain Specific Language (DSL) to describe the model to be generated (this is a really important but almost hidden Sparx EA capability).
  • Out of the box templates for transformation and generation - both working and as accelerators to create your own.

I suggest you start with this white paper for more details.

Now Google App Engine (GAE) has a database system - Datastore - quite simple to use but inevitably you will end up using or rolling a framework. I rolled my own and for each entity I need to create a number of simple classes each extending complicated base classes:

  • A Controller to CRUDL entities
  • A Factory to get the Controller
  • An Entity interface
  • A Server implementation designed to be used on the server side (wraps the Datastore Entity)
  • A Client implementation design to be used on the client side. I use it with Google Web Toolkit (GWT). This is a Plain Old Java Object.
  • I also need to generate GWT RMI Classes. 

This is a typical requirement for most frameworks (and I much prefer build time creation rather than SLOW runtime introspection etc.) This would be very boring to handcraft ... so I created a MDA transformation to a Java physical model. Then the actual Java code generation was done by the out-of-the-box Sparx EA templates.

Screenshots ... feel free to ask for more details.

This is an example of a logical model

This is a bit of the generated Java model just showing the client/shared classes

And this is some generated code examples - boring code to write manually ...

package xxx.generated.shared;

import org.architectureportal.gaeaddons.shared.BaseEntityClient;

/**
 * Patient Core Details
 * @author Adrian Sutherland
 * @version 1.0
 * @created 25-Mar-2014 18:30:06
 */
public class BasePatientEntityClient extends BaseEntityClient implements BasePatientEntity {

    /**
     * Postal Address
     */
    private String address;
    /**
     * Allergies
     */
    private String allergies;
    /**
     * Alternative Contact Details
     */
    private String alternativeContact;
    /**
     * Conditions
     */
    private String conditions;
    /**
     * Date of Birth
     */
    private java.util.Date dob;
    /**
     * Email Address
     */
    private String email;
    /**
     * Gender
     */
    private GenderType gender = GenderType.Unknown;
    /**
     * Mobile Number
     */
    private String mobile;
    /**
     * Patient Name
     */
    private String name;
    /**
     * NHS Number
     */
    private String nhsNmber;
    /**
     * Phone Number
     */
    private String phone;
    /**
     * Special Residence Instructions 
     */
    private String residenceInstructions;
    private static final long serialVersionUID = 1;
    /**
     * Special Instructions and Advice
     */
    private String specialNotes;
    /**
     * Brief Summary History
     */
    private String summaryHistory;

    /**
     * Constructor
     */
    public BasePatientEntityClient(){
        super();
        addSubtype(BasePatientEntity.SUBTYPE);
    
    }

    /**
     * Copy Constructor
     * 
     * @param source
     */
    public BasePatientEntityClient(BasePatientEntity source){
        super(source);
        address = source.getAddress();
        allergies = source.getAllergies();
        alternativeContact = source.getAlternativeContact();
        conditions = source.getConditions();
        dob = source.getDob();
        email = source.getEmail();
        gender = source.getGender();
        mobile = source.getMobile();
        name = source.getName();
        nhsNmber = source.getNhsNmber();
        phone = source.getPhone();
        residenceInstructions = source.getResidenceInstructions();
        specialNotes = source.getSpecialNotes();
        summaryHistory = source.getSummaryHistory();
    }

    /**
     * Getter for Postal Address
     */
    @Override
    public String getAddress(){
        return address;
    }

    /**
     * Getter for Allergies
     */
    @Override
    public String getAllergies(){
        return allergies;
    }

    /**
     * Getter for Alternative Contact Details
     */
    @Override
    public String getAlternativeContact(){
        return alternativeContact;
    }

    /**
     * Getter for Conditions
     */
    @Override
    public String getConditions(){
        return conditions;
    }

    /**
     * Getter for Date of Birth
     */
    @Override
    public java.util.Date getDob(){
        return dob;
    }

    /**
     * Getter for Email Address
     */
    @Override
    public String getEmail(){
        return email;
    }

    /**
     * Get the description of this BasePatient instance
     */
    @Override
    public String getEntityDescription(){
        return getName() + " (" + formatDate(getDob()) + ")";
    }

    /**
     * Get the description of the BasePatient type
     */
    @Override
    public String getEntityTypeDescription(){
        return BasePatientEntity.ENTITYDESC;
    }

    /**
     * Getter for Gender
     */
    @Override
    public GenderType getGender(){
        return gender;
    }

    /**
     * Getter for Mobile Number
     */
    @Override
    public String getMobile(){
        return mobile;
    }

    /**
     * Getter for Patient Name
     */
    @Override
    public String getName(){
        return name;
    }

    /**
     * Getter for NHS Number
     */
    @Override
    public String getNhsNmber(){
        return nhsNmber;
    }

    /**
     * Getter for Phone Number
     */
    @Override
    public String getPhone(){
        return phone;
    }

    /**
     * Getter for Special Residence Instructions 
     */
    @Override
    public String getResidenceInstructions(){
        return residenceInstructions;
    }

    /**
     * Getter for Special Instructions and Advice
     */
    @Override
    public String getSpecialNotes(){
        return specialNotes;
    }

    /**
     * Getter for Brief Summary History
     */
    @Override
    public String getSummaryHistory(){
        return summaryHistory;
    }

    /**
     * Setter for Postal Address
     * 
     * @param newVal
     */
    @Override
    public void setAddress(String newVal){
        if (isDifferent(this.address, newVal)) {
            this.address = newVal;
            setDirty(true);
        }
    }

    /**
     * Setter for Allergies
     * 
     * @param newVal
     */
    @Override
    public void setAllergies(String newVal){
        if (isDifferent(this.allergies, newVal)) {
            this.allergies = newVal;
            setDirty(true);
        }
    }

    /**
     * Setter for Alternative Contact Details
     * 
     * @param newVal
     */
    @Override
    public void setAlternativeContact(String newVal){
        if (isDifferent(this.alternativeContact, newVal)) {
            this.alternativeContact = newVal;
            setDirty(true);
        }
    }

    /**
     * Setter for Conditions
     * 
     * @param newVal
     */
    @Override
    public void setConditions(String newVal){
        if (isDifferent(this.conditions, newVal)) {
            this.conditions = newVal;
            setDirty(true);
        }
    }

    /**
     * Setter for Date of Birth
     * 
     * @param newVal
     */
    @Override
    public void setDob(java.util.Date newVal){
        if (isDifferent(this.dob, newVal)) {
            this.dob = newVal;
            setDirty(true);
        }
    }

    /**
     * Setter for Email Address
     * 
     * @param newVal
     */
    @Override
    public void setEmail(String newVal){
        if (isDifferent(this.email, newVal)) {
            this.email = newVal;
            setDirty(true);
        }
    }

    /**
     * Setter for Gender
     * 
     * @param newVal
     */
    @Override
    public void setGender(GenderType newVal){
        if (isDifferent(this.gender, newVal)) {
            this.gender = newVal;
            setDirty(true);
        }
    }

    /**
     * Setter for Mobile Number
     * 
     * @param newVal
     */
    @Override
    public void setMobile(String newVal){
        if (isDifferent(this.mobile, newVal)) {
            this.mobile = newVal;
            setDirty(true);
        }
    }

    /**
     * Setter for Patient Name
     * 
     * @param newVal
     */
    @Override
    public void setName(String newVal){
        if (isDifferent(this.name, newVal)) {
            this.name = newVal;
            setDirty(true);
        }
    }

    /**
     * Setter for NHS Number
     * 
     * @param newVal
     */
    @Override
    public void setNhsNmber(String newVal){
        if (isDifferent(this.nhsNmber, newVal)) {
            this.nhsNmber = newVal;
            setDirty(true);
        }
    }

    /**
     * Setter for Phone Number
     * 
     * @param newVal
     */
    @Override
    public void setPhone(String newVal){
        if (isDifferent(this.phone, newVal)) {
            this.phone = newVal;
            setDirty(true);
        }
    }

    /**
     * Setter for Special Residence Instructions
     * 
     * @param newVal
     */
    @Override
    public void setResidenceInstructions(String newVal){
        if (isDifferent(this.residenceInstructions, newVal)) {
            this.residenceInstructions = newVal;
            setDirty(true);
        }
    }

    /**
     * Setter for Special Instructions and Advice
     * 
     * @param newVal
     */
    @Override
    public void setSpecialNotes(String newVal){
        if (isDifferent(this.specialNotes, newVal)) {
            this.specialNotes = newVal;
            setDirty(true);
        }
    }

    /**
     * Setter for Brief Summary History
     * 
     * @param newVal
     */
    @Override
    public void setSummaryHistory(String newVal){
        if (isDifferent(this.summaryHistory, newVal)) {
            this.summaryHistory = newVal;
            setDirty(true);
        }
    }
}