Creating Internal DSLs in Java & Java 8
Adopting Martin Fowler's approach to domain-specific language.
Join the DZone community and get the full member experience.
Join For FreeCurrently I am reading this wonderful book on DSLs- Domain Specific Languages by Martin Fowler. The buzz around the DSLs, around the languages which support creation of DSLs with ease, the use of DSLs made me curious to know and learn about this concept of DSLs. And the exprience with the book so far has been impressive.
The Definition of DSL as stated by Martin Fowler in his book:
Domain-specific language (noun): a computer programming language of limited expressiveness focused on a particular domain.
DSL is nothing new, its been there for quite a long time. People used XML as a form of DSL. Using XML as a DSL is easy because we have XSD for validation of the DSL, we have parsers for parsing the DSL and we have XSLT for transforming the DSL into other languages. And most of the languages provide a very good support for parsing XMLs and populating their domain model objects. The emergence of languages like Ruby, Groovy and others have increased the adoption of DSL. For example Rails, a web framework written using Ruby, uses DSLs extensively.
In his book Martin Fowler classifies DSLs as Internal, External and Language Workbenches. As I read through the Internal DSL concepts I played around a bit with my own simple DSL using Java as the host language. Internal DSLs reside in the host language and are bound by the syntactic capabilities of the host language. Using Java as the host language didn’t give me really clear DSLs but I made an effort to get it to closer to a form where I could comprehend the DSL comfortably.
I was trying to create a DSL for creating a Graph. As far as I am aware of, the different ways to input and represent a graph are: Adjacency List and Adjacency Matrix. I have always found this difficult to use especially in languages like Java which don’t have matrices as first class citizens. And here I am trying to create an Inner DSL for populating a Graph in Java.
In his book, Martin Fowler stresses the need to keep the Semantic Model different from the DSL and to introduce a intermediate Expression builder which populates the Semantic Model from the DSL. By maintaining this I was able to achieve 3 different forms of the DSLs by writing different DSL syntax and expression builders and all the while using the same semantic model.
Understanding the Semantic Model
The Semantic Model in this case is the Graph
class which contains the list of Edge
instances and each Edge
containing from Vertex
, to Vertex
and a weight. Lets look at the code for the same:
Graph.java
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
public class Graph {
private List<Edge> edges;
private Set<Vertex> vertices;
public Graph() {
edges = new ArrayList<>();
vertices = new TreeSet<>();
}
public void addEdge(Edge edge){
getEdges().add(edge);
}
public void addVertice(Vertex v){
getVertices().add(v);
}
public List<Edge> getEdges() {
return edges;
}
public Set<Vertex> getVertices() {
return vertices;
}
public static void printGraph(Graph g){
System.out.println("Vertices...");
for (Vertex v : g.getVertices()) {
System.out.print(v.getLabel() + " ");
}
System.out.println("");
System.out.println("Edges...");
for (Edge e : g.getEdges()) {
System.out.println(e);
}
}
}
Edge.java
public class Edge {
private Vertex fromVertex;
private Vertex toVertex;
private Double weight;
public Edge() {
}
public Edge(Vertex fromVertex, Vertex toVertex, Double weight) {
this.fromVertex = fromVertex;
this.toVertex = toVertex;
this.weight = weight;
}
@Override
public String toString() {
return fromVertex.getLabel()+" to "+
toVertex.getLabel()+" with weight "+
getWeight();
}
public Vertex getFromVertex() {
return fromVertex;
}
public void setFromVertex(Vertex fromVertex) {
this.fromVertex = fromVertex;
}
public Vertex getToVertex() {
return toVertex;
}
public void setToVertex(Vertex toVertex) {
this.toVertex = toVertex;
}
public Double getWeight() {
return weight;
}
public void setWeight(Double weight) {
this.weight = weight;
}
}
Vertex.java
public class Vertex implements Comparable<Vertex> {
private String label;
public Vertex(String label) {
this.label = label.toUpperCase();
}
@Override
public int compareTo(Vertex o) {
return (this.getLabel().compareTo(o.getLabel()));
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
}
Now that we have the Semantic Model in place, lets build the DLSs. You should notice that I am not going to change my Semantic model. Its not a hard and fast rule that the semantic model shouldn’t change, instead the semantic model can evolve by adding new APIs for fetching the data or modifying the data. But binding the Semantic model tightly to the DSL will not be a good approach. Keeping them separate helps in testing the Semantic Model and the DSL independently.
The different approaches for creating Internal DSLs stated by Martin Fowler are:
- Method Chaining
- Functional Sequence
- Nested Functions
- Lambda Expressions/Closures
I have illustrated 3 in this post except Functional Sequence. But I have used Functional Sequence approach while using the Closures/Lambda expression.
Inner DSL by Method Chaining
I am envisaging my DSL to be something like:
Graph()
.edge()
.from("a")
.to("b")
.weight(12.3)
.edge()
.from("b")
.to("c")
.weight(10.5)
To enable the creation of such DSL we would have to write an expression builder which allows popuplation of the semantic model and provides a fluent interface enabling creation of the DSL.
I have created 2 expressions builders- One to build the complete Graph and the other to build individual edges. All the while the Graph/Edge are being built, these expression builders hold the intermediate Graph/Edge objects. The above syntax can be achieved by creating static method in these expression builders and then using static imports to use them in the DSL.
The Graph()
starts populating the Graph
model while the edge()
and series of methods later namely: from()
, to()
, weight()
populate the Edge
model. The edge()
also populates the Graph
model.
Lets look at the GraphBuilder which is the expression builder for populating the Graph
model.
public class GraphBuilder {
private Graph graph;
public GraphBuilder() {
graph = new Graph();
}
//Start the Graph DSL with this method.
public static GraphBuilder Graph(){
return new GraphBuilder();
}
//Start the edge building with this method.
public EdgeBuilder edge(){
EdgeBuilder builder = new EdgeBuilder(this);
getGraph().addEdge(builder.edge);
return builder;
}
public Graph getGraph() {
return graph;
}
public void printGraph(){
Graph.printGraph(graph);
}
}
And the EdgeBuilder which is the expression builder for populating the Edge
model.
public class EdgeBuilder {
Edge edge;
//Keep a back reference to the Graph Builder.
GraphBuilder gBuilder;
public EdgeBuilder(GraphBuilder gBuilder) {
this.gBuilder = gBuilder;
edge = new Edge();
}
public EdgeBuilder from(String lbl){
Vertex v = new Vertex(lbl);
edge.setFromVertex(v);
gBuilder.getGraph().addVertice(v);
return this;
}
public EdgeBuilder to(String lbl){
Vertex v = new Vertex(lbl);
edge.setToVertex(v);
gBuilder.getGraph().addVertice(v);
return this;
}
public GraphBuilder weight(Double d){
edge.setWeight(d);
return gBuilder;
}
}
Lets try and experiment the DSL:
public class GraphDslSample {
public static void main(String[] args) {
Graph()
.edge()
.from("a")
.to("b")
.weight(40.0)
.edge()
.from("b")
.to("c")
.weight(20.0)
.edge()
.from("d")
.to("e")
.weight(50.5)
.printGraph();
Graph()
.edge()
.from("w")
.to("y")
.weight(23.0)
.edge()
.from("d")
.to("e")
.weight(34.5)
.edge()
.from("e")
.to("y")
.weight(50.5)
.printGraph();
}
}
And the output would be:
Vertices...
A B C D E
Edges...
A to B with weight 40.0
B to C with weight 20.0
D to E with weight 50.5
Vertices...
D E W Y
Edges...
W to Y with weight 23.0
D to E with weight 34.5
E to Y with weight 50.5
Do you not find this approach more easy to read and understand than the Adjacency List/Adjacency Matrix approach? This Method Chaining is similar to Train Wreck pattern which I had written about sometime back.
Inner DSL by Nested Functions
In the Nested functions approach the style of the DSL is different. In this approach I would nest functions within functions to populate my semantic model. Something like:
Graph(
edge(from("a"), to("b"), weight(12.3),
edge(from("b"), to("c"), weight(10.5)
);
The advantage with this approach is that its heirarchical naturally unlike method chaining where I had to format the code in a different way. And this approach doesn’t maintain any intermediate state within the Expression builders i.e the expression builders don’t hold the Graph
and Edge
objects while the DSL is being parsed/executed. The semantic model remain the same as discussed here.
Lets look at the expression builders for this DSL.
//Populates the Graph model.
publicclassNestedGraphBuilder {
publicstaticGraph Graph(Edge... edges){
Graph g = newGraph();
for(Edge e : edges){
g.addEdge(e);
g.addVertice(e.getFromVertex());
g.addVertice(e.getToVertex());
}
returng;
}
}
//Populates the Edge model.
publicclassNestedEdgeBuilder {
publicstaticEdge edge(Vertex from, Vertex to,
Double weight){
returnnewEdge(from, to, weight);
}
publicstaticDouble weight(Double value){
returnvalue;
}
}
//Populates the Vertex model.
publicclassNestedVertexBuilder {
publicstaticVertex from(String lbl){
returnnewVertex(lbl);
}
publicstaticVertex to(String lbl){
returnnewVertex(lbl);
}
}
If you have observed all the methods in the expression builders defined above are static. We use static imports in our code to create a DSL we started to build.
Note: I have used different packages for expression builders, semantic model and the dsl. So please update the imports according to the package names you have used.
//Update this according to the package name of your builder
importstaticnestedfunction.NestedEdgeBuilder.*;
importstaticnestedfunction.NestedGraphBuilder.*;
importstaticnestedfunction.NestedVertexBuilder.*;
/**
*
* @author msanaull
*/
publicclassNestedGraphDsl {
publicstaticvoidmain(String[] args) {
Graph.printGraph(
Graph(
edge(from("a"), to("b"), weight(23.4)),
edge(from("b"), to("c"), weight(56.7)),
edge(from("d"), to("e"), weight(10.4)),
edge(from("e"), to("a"), weight(45.9))
)
);
}
}
And the output for this would be:
Vertices...
A B C D E
Edges...
A to B with weight 23.4
B to C with weight 56.7
D to E with weight 10.4
E to A with weight 45.9
Now comes the interesting part: How can we leverage the upcoming lambda expressions support in our DSL.
Inner DSL using Lambda Expression
If you are wondering what Lambda expressions are doing in Java, then please spend some time here before proceeding further.
In this example as well we will stick with the same semantic model described here. This DSL leverages Function Sequence along with using the lambda expression support. Lets see how we want our final DSL to be like:
Graph(g -> {
g.edge( e -> {
e.from("a");
e.to("b");
e.weight(12.3);
});
g.edge( e -> {
e.from("b");
e.to("c");
e.weight(10.5);
});
}
)
Yeah I know the above DSL is overloaded with punctuations, but we have to live with it. If you dont like it, then may be pick a different language.
In this approach our expression builders should accept lambda expression/closure/block and then populate the semantic model by executing the lambda expression/closure/block. The expression builder in this implementation maintain the intermediate state of the Graph
and Edge
objects in the same way we did in DSL implementation by Method Chaining.
Lets look at our expression builders:
//Populates the Graph model.
publicclassGraphBuilder {
Graph g;
publicGraphBuilder() {
g = newGraph();
}
publicstaticGraph Graph(Consumer<GraphBuilder> gConsumer){
GraphBuilder gBuilder = newGraphBuilder();
gConsumer.accept(gBuilder);
returngBuilder.g;
}
publicvoidedge(Consumer<EdgeBuilder> eConsumer){
EdgeBuilder eBuilder = newEdgeBuilder();
eConsumer.accept(eBuilder);
Edge e = eBuilder.edge();
g.addEdge(e);
g.addVertice(e.getFromVertex());
g.addVertice(e.getToVertex());
}
}
//Populates the Edge model.
publicclassEdgeBuilder {
privateEdge e;
publicEdgeBuilder() {
e = newEdge();
}
publicEdge edge(){
returne;
}
publicvoidfrom(String lbl){
e.setFromVertex(newVertex(lbl));
}
publicvoidto(String lbl){
e.setToVertex(newVertex(lbl));
}
publicvoidweight(Double w){
e.setWeight(w);
}
}
In the GraphBuilder
you see two higlighted lines of code. These make use of a functional interface, Consumer, to be introduced in Java 8.
Now lets make use of the above expression builders to create our DSL:
//Update the package names with the ones you have given
importgraph.Graph;
importstaticbuilder.GraphBuilder.*;
publicclassLambdaDslDemo {
publicstaticvoidmain(String[] args) {
Graph g1 = Graph( g -> {
g.edge( e -> {
e.from("a");
e.to("b");
e.weight(12.4);
});
g.edge( e -> {
e.from("c");
e.to("d");
e.weight(13.4);
});
});
Graph.printGraph(g1);
}
}
And the output is:
Vertices...
A B C D
Edges...
A to B with weight 12.4
C to D with weight 13.4
With this I end this code heavy post. Let me know if you want me to spit this into 3 posts- one for each DSL implementation. I kept it in one place so that it would help us in comparing the 3 different approaches.
To summarise:
- In this post I talked about DSL, Inner DSL as mentioned in the book Domain-Specific Languages by Martin Fowler.
- Provided an implementation for each of the three approaches for implementing the Inner DSLs are:
Published at DZone with permission of Mohamed Sanaulla, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments