Friday, June 18, 2010

Generating an internal DSL from an external DSL

We have learned that when we create sound explicit intension revealing APIs the number of WTFs per minute is reduced and the programmer's developer life is a bit easier.

Further we can introduce a steep learning curve and make less faults due to accidental missusage of the API.

Making internal DSLs formulated in our general purpose programming language is one way to aproach this but writing an internal DSL is not an easy task and violates the KISS principle.

But since you want to make the life of our developers a bit easier and even more fun it makes sense to create such an internal DSL where the developer can declaratively describe what is needed from the API.

actualValue = ... // this value should be detected by the framework
Detector detector = actualValueFramework.bind(new DetectorModule {
@Override
public void configure(DetectorExpressionBuilder detector) {
detector.detect()
.withEvent().change()
.andInterval().humanVisual()
.andPersistency().disc();
}
}).to(actualValue);

Listing 1

My prefered internal DSL style in Java is shown in listing 1 and is known as Method Chaining.

With code completion from the IDE this is very neat since the proposed possibilities from the completion express the further declaration steps. The developer can explore the API without any prior knowledge of its usage.

So this way I can achieve the goals mentioned above. Exploring is fun. Code completion while exploring makes it easy. Finding words from the problem domain makes it intension revealing.

But how can I make my own life easier and even more fun?

I have learned that the internal DSLs in Java can have a generic domain model, a meta-model. There is always a variation-point and its variations. Variations can be defined using polymorphism. A generic domain model must therefore give me the ability to express my API this way.

In the example above I have an Domain-Event that can be either Change, reach an Upper or Lower Limit. Then there is an interval that can be a millisecond, humanVisual oder oneMinute. The persistency can be disc or memory.

Event, interval and persistency are the variation-points where as change, upper, lower, millisecond, humanVisual, oneMinute, disc and memory are variations.

Using actifSource I created an external DSL that lets me express this variability in a generic domain model and write a code generator that generates ExpressionBuilders from the specific domain model.

Now it is no more hard to write such type of internal DSL where your specific domain model can be expressed in a declarative descriptive way.

How is it done using actifSource?

On the generic domain model class ExpressionBuilder is defined one property called operation and one called expression.

The property operation is from type Operation and expression is from type Class.

Operation is a NamedResource and here the operation name is used as a starting point of the expression. As in listing 1 the detector.detect() method. The Class contains a Class Instance having defined its own properties. Each property's Range is from an abstract class.

The Class is the variation-point where as the Class Instance is the variation.


public PersistencyExpression withPersistency(){
return new PersistencyExpression();
}


Listing 2

The generator reads each property and defines a with-Method as shown in Listing 2 for
i.e. the Persistency variation-point.

Further the generator generates an ExpressionBuilder by looking up all instances of the type defined as Range on the property and creates methods.


public class PersistencyExpression{
public AndProperties memory(){
persistency = new Memory();
return new AndProperties();
}
public AndProperties disc(){
persistency = new Disc();
return new AndProperties();
}
}

Listing 3

The end declaration of the internal DSL expression is also generated as shown in listing 4.


public Detector build(){
DetectorImpl build = new DetectorImpl();

if(persistency!=null){
build.setPersistency(persistency);
}
if(interval!=null){
build.setInterval(interval);
}
if(range!=null){
build.setRange(range);
}
if(event!=null){
build.setEvent(event);
}

return build;
}


Listing 4


public class ActualValueFrameworkImpl ...
public BindTo bind(final DetectorModule detectorModule) {
return new BindTo(){

@Override
public Disposable to(final ActualValue actualValue) {
DetectorExpressionBuilder detect = new DetectorExpressionBuilder();
detectorModule.configure(detect);
return detect.build();
}
};
}
...
}

Listing 5

Now the framework method looks like listing 5.

If this is a proper usage of modeling or not... it does make my life easier an I can generate several internal DSLs using this aproach and have nothing else to do as to declaratively define it and press generate.

Happy modeling, Nils

No comments:

Post a Comment