DSL Tools: A Complex Solution for a Simple Problem

Published 24 July 07 09:43 PM | andersnoras 

Last night I was unable to sleep, so I started reading Domain Specific Development with Visual Studio DSL Tools and I was immediately reminded of my rant about software factories. Just like with the software factories provided by the Patterns and Practices team, the examples in the book are bad examples of what you should use a visual DSL for. The book has an introductory narrative which guides us through how the fictional consultancy CJKW creates a DSL to customize the ASP.NET Issue Tracker starter kit. CJKW's motivation for doing this is that their various clients have different requirements for which states that apply to an issue and the manual modifications for the different installations have become increasingly difficult to maintain.

The lead role in the story is played by Devika, a lead programmer at CJKW. The narrative leads us through the entire process from creating a DSL project, defining the DSL definition with all its elements and constraints, customizing the design time experience, creating templates for the modifications that have been manual to packaging and deploying the DSL on developer computers. All in all this is a lengthy and complex undertaking. The irony of the story is that after Devika has spent a fair share of time developing the DSL, CJKW's business analyst objects to the design because most clients want to change the states and transitions midway through a project. Regenerating the entire application from the DSL just won't be acceptable to their clients. Luckily, Archie (CJKW's big-A Architect) steps in at the last moment and suggest that the issue tracking application should be rewritten to be more generic, storing the allowed states and transitions in the issues database itself. Devika's DSL can be tailored to populate these tables and the day is saved. Everybody is happy, but should they be?

The story shows us how to create an utterly complicated solution for a simple problem. There is no example of how CJKW retrieve the states and transitions from a database so let's take a look at the code generated by Devika's DSL (which is similar to the manual hacks CJKW had done to satisfy their clients before they discovered DSLs).

string[] nextOptions=new string[] {};
if (!string.IsNullOrEmpty(currentState))
{
    if ("Unassigned"==currentState)
    {
        nextOptions=new string[]
        {
            "Unassigned", // allow leaving state unchanged
            "Assigned",
            "Rejected"
        };
    }
    if ("Approved"==currentState)
    {
        nextOptions=new string[]
        {
            "Approved", // allow leaving state unchanged
            "Fixed"
        };
    }
    if ("Rejected"==currentState)
    {
        nextOptions=new string[]
        {
            "Rejected" // allow leaving state unchanged
        };
    }
    if ("Fixed"==currentState)
    {
        nextOptions=new string[]
        {
            "Fixed" // allow leaving state unchanged
        };
    }
}
else
{
    // New issue
    nextOptions=new string[] {"Unassigned"};
}

A couple of things to notice here is that the code is procedural and repetitive. The reason for this is that the code is generated from a template and it is much easier to generate a single procedure than an object model. Astute readers might also have noticed the bug in the procedure. When the state changes from "Unassigned" to "Assigned" we reach a dead end, there are no subsequent states following the "Assigned" state. These sorts of bugs can easily crawl into code that suffers from primitive obsession.

In my rant on software factories I stated that we should prefer frameworks over code generation, and whenever we see a reoccurring problem we should try to commoditize it via a framework rather than through code generation to improve maintainability.The same goes for generative DSLs. Let's refactor!

We'll start by creating a kind of class I'm not particularly fond of, the helper class, but its the best option here since the code is primitive obsessed.

public class StateTransition
{
    private string initialState;
    private Dictionary<string, string> transitions;
    public StateTransition(string initialState, Dictionary<string, string> transitions)
    {
        this.initialState = initialState;
        this.transitions = transitions;
    }
    public string[] GetSuccessiveStates(string currentState)
    {
        if (string.IsNullOrEmpty(currentState))
        {
            return new string[] { initialState };
        }
        if (!transitions.ContainsKey(currentState))
        {
            return new string[0];
        }
        return transitions[currentState].Split(',');
    }
}

Rather than newing up instance of the helper class, we'll use inversion of control to create the new instances. Below is the Castle Windsor configuration for the component.

<component
	id="StateTransition"
	type="Example.StateTransition, StateTransitionExample">
  <parameters>
    <initialState>Unassigned</initialState>
    <transitions>
      <dictionary keyType="System.String, mscorlib" valueType="System.String, mscorlib">
        <entry key="Unassigned">Unassigned,Assigned,Rejected</entry>
        <!-- I wonder what happens if we go from Unassigned to Assigned :P -->
        <entry key="Approved">Approved,Fixed</entry>
        <entry key="Rejected">Rejected</entry>
        <entry key="Fixed">Fixed</entry>
      </dictionary>
    </transitions>
  </parameters>
</component>				

Rather than going down the enterprisey route of storing the states and transitions in the database we're keeping them in the configuration file.

Next, we get rid of all the code generated by the DSL and replace it with this:

StateTransition stateTransition = IoC.Resolve<StateTransition>();
string[] nextOptions = stateTransition.GetSuccessiveStates(currentState);

We have now satisfied both of the requirements that lead to the creation of the DSL; maintainability (less code, no string literals in code and more) and configurability (states and transitions pulled out from the application). The cool thing is that we've done this in a fraction of the time it would have taken to create the DSL and make the required changes to the Issue Tracking application.

The state transition bug (from "Unassigned" to "Assigned") has intentionally been left in. Let's look at how we use the State pattern to prevent glitches like this from creeping into our code.

image

Above you can see a class diagram of the design. This pattern allows a class to change its behavior when its state changes, and it is a brilliant example of why you should prefer composition over inheritance.
When a client creates an instance of the Issue class, its state field is set to an instance of the UnassignedState class. Whenever the client class a method on the Issue, these invocations are delegated to the IssueState instance assigned to the state field. Depending on which state we are in, the behavior of the Issue class changes. Below is an example of a concrete IssueState:

public class UnassignedState : IssueState
{
    public UnassignedState(Issue issue) : base(issue)
    {
    }
    public override void Assign()
    {
        issue.State = new AssignedState(issue);
    }
    public override void Reject()
    {
        issue.State = new RejectedState(issue);
    }
}

I reckon that changing the state leads to other things than just updating a database column, so something along the lines of this design would probably have been more appropriate for the Issue Tracker application than the procedural design it has. This is one of the big issues with the examples provided, they encourage hard to maintain procedural code because this is easy to generate.

My post on software factories got some feedback from Microsoft on how great software factories are. There is nothing wrong with the guidance automation or DSL Tools themselves, but I would really like to see some guidance on how to use them that show how they add value rather than how to create second rate solutions for problems that have much simpler solutions. If you want to see a good application of the DSL Tools I recommend Gokhan Altinoren's ActiveWriter which is a visual DSL for generating ActiveRecord classes.

Filed under: , , ,

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

# Some good OO design at Dazed & Confused said on July 26, 2007 3:19 AM:

PingBack from http://blog.f12.no/wp/2007/07/26/some-good-oo-design/

# Peter Bell said on July 26, 2007 8:42 AM:

To me, the most important thing is the efficacy of the DSLs - not whether you happen to compile or interpret them (use a code generator or a framework). I've gone back and forth on this and if you design your solution well, you should be able to refactor from code gen to run time interpretation via a framework and back again without having to change any of the statements in your DSLs.

On the whole I prefer runtime interpretation of DSLs via a framwork, but if you want to protect IP, improve a proven performance bottleneck or do some things your language doesn't allow you to do dynamically (there is a reason you have so much more code generation in Java than in Ruby!), code gen sometimes makes sense.

Some thoughts on the topic:

      http://www.pbell.com/index.cfm/2007/6/3/Metaprogramming-Generation-vs-Interpretation

# Steven Kelly said on August 14, 2007 5:25 AM:

I'd tend to agree that I find many of the ways Microsoft suggest using their DSL Tools to be odd, and not really good examples of how or where to apply Domain-Specific Modeling. There are however two things to bear in mind before criticising the book too heavily.

Firstly, as I mention in my own mini-review of the book --

http://www.metacase.com/blogs/stevek/blogView?entry=3358168014

-- the purpose of the book is primarily to teach DSL Tools. The examples may therefore be simplified, even to the point of being poor DSM practice, to keep them simple enough that the main focus is on the tools.

Secondly, it's often vital to the acceptance of a DSL that it generate code that looks the same as existing code. The normal pattern for moving to DSM is straight coding => components => framework => textual DSL or XML => visual DSM language. There's a nice picture of that development by Don Roberts and Ralph Johnson (see Fig 1 in the PDF link at top right):

http://citeseer.ist.psu.edu/roberts96evolving.html

With that history, the code to be generated tends to be pretty well refactored already -- so hopefully rather better than the code Devika is working with!

In our experience at MetaCase of over a decade of helping people apply DSM, the best initial step is to generate the same code. That helps their developers to "get it", and removes many fears and prejudices caused by the appalling mess most fixed code generators lead to. After that, when some models have already been made, it is easy to improve the generator to produce better code. For instance, generating your IssueState subclasses would be a simple task in our MetaEdit+ (but IIRC considerably harder in DSL Tools, since they don't deal well with a single generator producing multiple files). Obviously, it's much easier to change every developer's code to the new format by generation, rather than by sending them a memo!

Code generation is only really palatable when you never have to edit the generated code, best if you never look at it. That way, you really are raising the level of abstraction, rather than just saving some keystrokes now but risking trouble later. If you want to see some real examples of DSM -- ones that work in the real world – take a look here, particularly "Sample DSM models":

http://www.dsmforum.org/cases.html

# andersnoras said on August 15, 2007 4:17 AM:

@Steven;

Don't get me wrong, I think the book is the missing manual for DSL Tools and well worth its price tag if you think about using the tools to create a visual DSL for Visual Studio. I consider defining a DSL, particularly a visual one, as something you should think twice about before you do it because the effort required to create one is huge.

I fully agree that code generation fits best when there is no need to manually maintain the generated code, but unfortunately there are too few tools that cater for this. As you point out, many of the examples in the DSL Tools book require the reader to make such changes.

Even if it is a bit unfair to pick on an example from a book that is meant to be a guide to using a toolset, rather than a book on good software design, I believe that good design can eliminate the need for code generation or other DSL support. I care strongly about maintainability, and raising the level of abstraction of the libraries themselves is preferable to using other means IMHO.

# Anders Norås' Blog said on January 30, 2008 12:52 PM:

Today it is exactly one year since I pick up on blogging after a long break. To celebrate, I&#8217;ll

Leave a Comment

(required) 
(optional)
(required) 
Enter the code you see below