Yesterday, we gave a presentation at Cologne Intelligence about domain specific languages (DSL) in software projects. The basic idea is: Instead of writing the whole Java source code manually, parts of the project are implemented with a DSL and automatically transformed into a destination language. This article sums up the results.
Using a DSL has several advantages:
- Modern software architectures consist of components like Resources, Controllers, Services, Repositories, etc. Very often, adding an additional method leads to a lof of boiler plate code which has to be written manually and is therefore very time consuming. Automated code generation helps to speed up the development process.
- Usually, domain experts participate in the software development process but have difficulties in understanding the corresponding source code. A DSL hides the details of general purpose programming languages and allows domain experts to design parts of the project.
The following example demonstrates the approach. Given is a software system for medical doctors:
The interfaces of the Java project are shown above. There is a Resource object which represents the the REST interface. The office PC connects to this interface and allows employes to edit patient data, order medicals, etc. Each Resource passes the control flow to a controller. A Controller orchestrates a set of Service objects which call Repositories and DAO mappers (not part of the diagram). The approach has two problems: When a new method is added to the Resource, the corresponding Controller and Service classes have to be adjusted as well. Furthermore, a domain expert like a medical doctor is not familiar with UML diagrams and has problems understand the system features.
We do not want to implement this architecture manually. Instead, a domain specific language is added to the project. A model can look like this:
employee Doctor{
"orderMedicine", "Order Medicine, Bandage, etc. for the Office";
}
employee Receptionist{
"managePatient", "Create and edit Patient Data";
}
room Office{
Doctor
Receptionist
}
The model allows us to specify rooms and employes. Each employee can perform several tasks. Additionally, a description can be added to each task. An employee is associated with a set of rooms. In our case, there is a single office which is used by receptionists and doctors. If we want to use this DSL in our project, two things are necessary:
- We have to implement a parser to convert our model into an abstract syntax tree (AST).
- We have to implement a transformator to convert our model into Java source code.
Doing these things manually is time consuming and knowledge about compiler design is necessary. Luckily, the Xtext Framework exists. It is part of Eclipse and provides two languages:
- Xtext allows you on a very simple way to describe a grammar, generate a parser, an Eclipse plugin with syntax highlighting, mouse over tooltips and syntax verification.
- Xtend is a template language to access the AST and transform it into a destination language like Java.
The following Xtext grammar describes the medical DSL from above:
Model:
employees+=Employee+
rooms+=Room+;
Room:
'room' name=ID '{'
employees+=[Employee]*
'}';
Employee:
'employee' name=ID '{'
tasks+=Task*
'}';
Task:
task=STRING "," description=STRING ';'
;
Like any other grammar and parser generators, it consists of a set of rules. The starting rule “Model” derives to a set of Employees and Rooms. Due to the +, a model has to contain at least one room and one employee (in contrast to the * which marks a 0..n relationship). A room has employees who work there and perform different tasks. The [] brackets ensure that only previously specified employees can be referenced.
As previously described, the Xtext framework reads this grammar and generates a parser and a language plugin for your IDE. Although this is quite nice, it’s useless without an automated translation into a target language. Therefore, we use Xtend to describe a Java transformator:
override void doGenerate(Resource r, IFileSystemAccess2 fsa, IGeneratorContext context) {
for (room : r.allContents.toIterable.filter(Raum)) {
fsa.generateFile("controller/Abstract" + room.name + "Controller.java", room.compile)
}
}
def compile(Room room)'''
package controller;
import services.*;
abstract class Abstract«room.name»Controller{
«FOR b : room.employees»
I«b.name»Service «b.name.toLowerCase»;
«ENDFOR»
«FOR b : room.employees»
«FOR t : b.tasks»
/**
* «t.description»
*/
public void «t.task»();
«ENDFOR»
«ENDFOR»
}
'''
Parts of Xtend look and feel like Java, other elements are more like a template language. The method doGenerate() is called at the beginning of the transformation process. It iterates through a list of Room objects. For each Room, compile() is invoked. The results of compile() are written into the destination file AbstractRoomnameController.java. The french punctuation marks «» are used to access attributes of the Room object, implement loops and conditions. Our Xtend example generates references to the corresponding service implementations and method prototypes. A generated AbstractOfficeController would look like this:
package controller;
import services.*;
IDoctorService doctor;
IReceptionistService receptionist;
/**
* Order Medicine, Bandage, etc. for the Office
*/
public void orderMedicine();
/**
* Create and edit Patient Data
*/
public void managePatient();
As a conclusion, Xtext and Xtend enabled us to write a small DSL to automate code generation within 1-2 hours. The DSL allows domain experts to specifiy behaviour and descriptions which are automatically converted into Java source code. The Eclipse plugins which are automatically created by the Xtext framework can be deployed among the development team and provides syntax highlighting and basic syntax verification.